1

I wanted to write generic code that returns Sql Server procedure results as DataTable objects. I have then written the code below, but, I stumbled across the problem of DataTable.Rows.Add() expecting a params object[] parameter, while I what I have to pass is a IEnumerable<string>.

    public static DataTable GetProcedureResults(int id)
    {
        return GetQueryResultAsDataTable<MyProcedure_Result>(_sqlServerDB.MyProcedure(id));
    }

    private static DataTable GetQueryResultAsDataTable<T>(ObjectResult<T> objectResult)
    {
        DataTable resultAsDataTable = new DataTable("Result");
        var columns = typeof(T).GetProperties();
        foreach (var column in columns)
        {
            resultAsDataTable.Columns.Add(column.Name, typeof(string));
        }

        foreach (var resultRecord in objectResult)
        {
            var fields = resultRecord.GetType().GetProperties().Select(p => p.GetValue(resultRecord).ToString());
            resultAsDataTable.Rows.Add(fields);
        }

        return resultAsDataTable;
    }

For those who are familiar with this issue, you may have guessed that my datatable at the end looks like:

enter image description here

The values are all treated as a single object, thus are all being inserted in the 1st column.

What is the workaround here, if any ? I read in other posts that seems C#6 will fix this issue, other than that did not find a clear and concise solution.

Veverke
  • 9,208
  • 4
  • 51
  • 95
  • Can't you just do `resultAsDataTable.Rows.Add(fields.ToArray());` ? – Me.Name Nov 04 '15 at 16:14
  • @DavidL: Clarifying: I wanted to "flatten" the IEnumerable so that it turns into a params T[] object and thus the datatable is populated correctly. Ex: if my IEnumerable is List {1, 4, 5, 6} I would want it to be treated as datatable.Rows.Add(1, 4, 5, 6); – Veverke Nov 04 '15 at 16:14
  • Allowing to pass an IEnumerable for a params args was only a proposal for C#6 ; but it didn't make it into the final release. – Sehnsucht Nov 04 '15 at 16:16

2 Answers2

5

If you have an IEnumerable<T> and a method expects a T[], just call ToArray on your enumerable.

var fields = resultRecord.GetType().GetProperties().Select(p => p.GetValue(resultRecord).ToString());
resultAsDataTable.Rows.Add(fields.ToArray());

The ability to define params parameters of IEnumerable<T> rather than T[] did not end up making it into C# 6 (see Eric Lippert's answer about the feature, which links to the announcement of the feature being removed from C# 6). However, even with that theoretical feature, it wouldn't help in this case. That feature would allow the designer of an API (e.g. the person writing DataRowCollection.Add) to accept an IEnumerable<T> rather than T[] if they so choose (in this case, they almost certainly wouldn't, since it would be a silent breaking change). It does not allow you to pass an IEnumerable<T> to a method expecting a T[]. As the caller of the method, if you're not utilizing the params attribute of the parameter (i.e. passing individual arguments rather than a collection), then it's just a "normal" method that takes an array, and you're in charge of converting your argument to the expected type.

Community
  • 1
  • 1
bdukes
  • 152,002
  • 23
  • 148
  • 175
  • Wow, that simple ? what have I been doing... this does it. Thanks! – Veverke Nov 04 '15 at 16:16
  • Let me understand: what difference does it make for the compiler receiving an IEnumerable or an array ?! Why he able to flatten the sequence only if it is an array and not when it is an IEnumerable ? I was with the impression that the problem was that the compiler was unable to *flatten* an IEnumerable sequence and make it like e1, e2, e3, e4 - which is what a params object[] object should look like. How come it is limited do it for an array but not for an IEnumerable ? – Veverke Nov 04 '15 at 16:19
  • the difference ; an IEnumerable isn't an array ; so when expecting an array an IEnumerable won't fit (the other way works though because an array IS an IEnumerable). As what is an IEnumerable, put simply it's an object which will yield (later) values when enumerated, it doesn't contains the values directly ; it knows how to give them when requested (via foreach for example). Didn't understood the part about flattening, flattening is more when you have a container of containers and you want to have a single container with all the containers values concated somehow. – Sehnsucht Nov 04 '15 at 16:28
  • Bottom line is that the **params** keyword is not magic: what is expected is an array object (due the syntax that follows the keyword **params** - the use of [ ]) so the only thing required is simply an IEnumerable to array conversion like we are required to do everywhere else. – Veverke Nov 04 '15 at 16:30
  • basically yes, params allows you to pass multiple values that "appear" as multiples arguments but under the hood, compiler just make the array and pass it as a whole for you (or use the one you provide if you directly gave him one) – Sehnsucht Nov 04 '15 at 16:32
  • @Sehnsucht: bottom line is that I mixed things up. The keyword **params** makes no difference in determining the type of the parameter being passed - the type is supposed to be an array, always. The keyword is simply a CLR shortcut that spares us from formally creating an array object - and instead just give the elements on the fly. I immediately thought my problem was the fact the the method expects a "params" parameter, I do not know why. Which is obviously wrong, since there is no such thing in the first place. – Veverke Nov 04 '15 at 16:33
1

You can actually pass an array into a params object[], so the temporary solution may just be:

var fields = resultRecord.GetType().GetProperties()
                                   .Select(p => p.GetValue(resultRecord).ToString())
                                   .ToArray();
resultAsDataTable.Rows.Add(fields);
lsedlacek
  • 313
  • 2
  • 6