34

I'm using a third party library which returns a data reader. I would like a simple way and as generic as possible to convert it into a List of objects.
For example, say I have a class 'Employee' with 2 properties EmployeeId and Name, I would like the data reader (which contains a list of employees) to be converted into List< Employee>.
I guess I have no choice but to iterate though the rows of the data reader and for each of them convert them into an Employee object that I will add to the List. Any better solution? I'm using C# 3.5 and ideally I would like it to be as generic as possible so that it works with any classes (the field names in the DataReader match the property names of the various objects).

Anthony
  • 7,210
  • 13
  • 60
  • 70
  • Damn, wish I was near a compiler right now, I would love to write this code! I will have crack at it tomorrow if no-one else blows me away. +1 question. – Matt Howells Jul 29 '09 at 20:53
  • @MattHowells you can still write it, personally would love to see if its something different. – nawfal Feb 11 '13 at 17:57
  • possible duplicate of http://stackoverflow.com/questions/1464883/how-can-i-easily-convert-datareader-to-listt – nawfal Feb 11 '13 at 18:17

12 Answers12

71

Do you really need a list, or would IEnumerable be good enough?

I know you want it to be generic, but a much more common pattern is to have a static Factory method on the target object type that accepts a datarow (or IDataRecord). That would look something like this:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }

    public static Employee Create(IDataRecord record)
    {
        return new Employee
        {
           Id = record["id"],
           Name = record["name"]
        };
    }
}

.

public IEnumerable<Employee> GetEmployees()
{
    using (var reader = YourLibraryFunction())
    {
       while (reader.Read())
       {
           yield return Employee.Create(reader);
       }
    }
}

Then if you really need a list rather than an IEnumerable you can call .ToList() on the results. I suppose you could also use generics + a delegate to make the code for this pattern more re-usable as well.

Update: I saw this again today and felt like writing the generic code:

public IEnumerable<T> GetData<T>(IDataReader reader, Func<IDataRecord, T> BuildObject)
{
    try
    {
        while (reader.Read())
        {
            yield return BuildObject(reader);
        }
    }
    finally
    {
         reader.Dispose();
    }
}

//call it like this:
var result = GetData(YourLibraryFunction(), Employee.Create);
kumarharsh
  • 18,961
  • 8
  • 72
  • 100
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • I'm definitely of the same sentiment regarding returning an `IEnumerable` and then perhaps calling `ToList`. – Noldorin Jul 29 '09 at 21:06
  • It's even easier than I thought back then: in many cases, the generic method can infer the type based on the delegate you pass to it. – Joel Coehoorn Jul 18 '12 at 15:25
  • Since "yield return" defers the execution, the updated version didn't work for me because by the time it executes, the DataReader is already closed. It's still a good example, though. – Korey Apr 03 '13 at 18:56
  • @Korey I've used this pattern in a few other places here on Stack Overflow, where the difference is that instead of accepting a datareader as an argument to a function, I would change the code at the location where the datareader is first created to return an IEnumerable instead of datareader or something else. – Joel Coehoorn Apr 03 '13 at 19:19
  • @JoelCoehoorn, in my use case, I would probably call ToList() immediately, but it makes me wonder: I've basically cast SqlDataReader several times into IDataRecords, but then SqlDataReader is `Dispose()`ed once ToList() is called right? Are the IDataRecords in my List still valid? Are they possibly preventing memory from being freed properly? Are they ticking time bombs waiting for the garbage collector to come by and erase them? Maybe I should make a separate question about this very thing. Thanks. – user1325179 Sep 25 '14 at 20:45
  • @user1325179 This is the reason why the BuildObject Func is included in the argument list. That function constructs a new BusinessLayer object from the IDataRecord that is independent of the datareader. This isn't important because of `ToList()` as much as because without it, the code yields on the _same object_ that just mutates with each iteration. The memory for the object in a list would still be available after disposal (disposal is about unmanaged resources, not managed memory), but the list would have the same object for every entry. `BuildObject()` takes care of this. – Joel Coehoorn Sep 25 '14 at 21:18
  • @JoelCoehoorn, I commented about the use of ToList() since it would end up calling Dispose() before I had gotten a chance to use any of the IDataRecords. Isn't that true? But if we can transfer the data to another object, like with BuildObject, the data should be safe, right? The transfer would need to copy values, not just references to the SqlDataReader. So what if, instead of using a BuildObject Func, after the call to ExecuteReader(), I called reader.Cast().ToList()? Would that be sufficient to separate the data from the reader which will be disposed? – user1325179 Sep 25 '14 at 21:25
  • @user1325179 No, that's exactly true. The BuildObject function will be called on each element as the records are enumerated into the list, _before_ the datareader is disposed. Even if that didn't happen, dispose only destroys unmanaged resources. The memory used by the datareader is still there, including the final state of the fields used by the IDataRecord – Joel Coehoorn Sep 25 '14 at 21:37
  • @JoelCoehoorn, thanks for your patience. I understand that BuildObject will be called before Dispose, but once ToList() is called, all the `yield` commands will have been called, and the using statement will end, disposing of the SqlDataReader. But I guess the key point is that the SqlDataReader's data is still available is even after it has been disposed. I never knew that, and I just confirmed it elsewhere. Thank you. So is the call `reader.Cast().ToList()` a good idea, or is there a better way to convert the SqlDataReader data to List? – user1325179 Sep 25 '14 at 21:45
  • @user1325179 The key here is that this does should _not_ make a List. The BuildObject() function _needs_ to copy the data into a new object. If you don't do that, then yes: this will fail. Also, somewhere along the way there was a `GetEnumerator()` method that makes this pretty much obsolete. A lot of the time you can just use that, instead. – Joel Coehoorn Sep 25 '14 at 21:47
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/61940/discussion-between-user1325179-and-joel-coehoorn). – user1325179 Sep 25 '14 at 22:05
  • I have to disagree that this is a good solution. Return types are promises to the caller. IEnumerable means: "A way to fetch T's on demand". I interpret "on demand" to mean, "any time I like, not necessarily all at once". However - if you attempt to enumerate at your pleasure, specifically, after the connection is closed, you will encounter a problem. – Kyle Pena Apr 23 '15 at 14:14
  • @KylePena It's possible to write DB methods that return IEnumerable and keep the connection open until the Enumerable is disposed. – Joel Coehoorn Apr 23 '15 at 17:41
  • 1
    @JoelCoehoorn, i think ,Id = record["id"], should be converted to int from object?? else there would be an exception!! – Shekhar Pankaj Jul 17 '15 at 11:42
  • Excellent, Amazing.. brilliant update, and thinking about a generic version.. You deserves much upvotes +1 – ThePravinDeshmukh Sep 02 '15 at 07:45
  • I think calling Dispose on an object you been given as a parameter is a bad idea, as it conceals from the caller that they can no longer use the object they gave you. – Richardissimo Feb 23 '18 at 05:41
25

You could build an extension method like:

public static List<T> ReadList<T>(this IDataReader reader, 
                                  Func<IDataRecord, T> generator) {
     var list = new List<T>();
     while (reader.Read())
         list.Add(generator(reader));
     return list;
}

and use it like:

var employeeList = reader.ReadList(x => new Employee {
                                               Name = x.GetString(0),
                                               Age = x.GetInt32(1)
                                        });

Joel's suggestion is a good one. You can choose to return IEnumerable<T>. It's easy to transform the above code:

public static IEnumerable<T> GetEnumerator<T>(this IDataReader reader, 
                                              Func<IDataRecord, T> generator) {
     while (reader.Read())
         yield return generator(reader);
}

If you want to automatically map the columns to properties, the code idea is the same. You can just replace the generator function in the above code with a function that interrogates typeof(T) and sets the properties on the object using reflection by reading the matched column. However, I personally prefer defining a factory method (like the one mentioned in Joel's answer) and passing a delegate of it into this function:

 var list = dataReader.GetEnumerator(Employee.Create).ToList();
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
3

Whilst I wouldn't recommend this for production code, but you can do this automatically using reflection and generics:

public static class DataRecordHelper
{
    public static void CreateRecord<T>(IDataRecord record, T myClass)
    {
        PropertyInfo[] propertyInfos = typeof(T).GetProperties();

        for (int i = 0; i < record.FieldCount; i++)
        {
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (propertyInfo.Name == record.GetName(i))
                {
                    propertyInfo.SetValue(myClass, Convert.ChangeType(record.GetValue(i), record.GetFieldType(i)), null);
                    break;
                }
            }
        }
    }
}

public class Employee
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public DateTime? BirthDate { get; set; }

    public static IDataReader GetEmployeesReader()
    {
        SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString);

        conn.Open();
        using (SqlCommand cmd = new SqlCommand("SELECT EmployeeID As Id, LastName, BirthDate FROM Employees"))
        {
            cmd.Connection = conn;
            return cmd.ExecuteReader(CommandBehavior.CloseConnection);
        }
    }

    public static IEnumerable GetEmployees()
    {
        IDataReader rdr = GetEmployeesReader();
        while (rdr.Read())
        {
            Employee emp = new Employee();
            DataRecordHelper.CreateRecord<Employee>(rdr, emp);

            yield return emp;
        }
    }
}

You can then use CreateRecord<T>() to instantiate any class from the fields in a data reader.

<asp:GridView ID="GvEmps" runat="server" AutoGenerateColumns="true"></asp:GridView>

GvEmps.DataSource = Employee.GetEmployees();
GvEmps.DataBind();
Dan Diplo
  • 25,076
  • 4
  • 67
  • 89
  • What wouldn't you recommend it for production? – Anthony Jul 30 '09 at 07:55
  • 1
    Because it "feels" wrong. Automatically setting properties from a datareader means you have less control over error checking and using reflection is expensive. I don't feel it's robust, though it should work. However, if you are serious about using this kind of technique then you'd be best looking a proper ORM mapping solution, such as LinqToSql, Entity Framwork, nHibernate etc. – Dan Diplo Jul 30 '09 at 10:03
  • @DanDiplo exactly! this is what most mappers prefer to do, and only we programmers know how brittle it is. we should think about domain objects and meta programming should be the last to resort! – nawfal Feb 05 '13 at 19:32
  • Use some caching though to improve reflection performance, as shown [here](http://www.simple-talk.com/dotnet/.net-framework/a-defense-of-reflection-in-.net/) the basic idea is to not use reflection inside the loop.. – nawfal Feb 05 '13 at 19:47
  • I'm not sure I agree with the proposed brittleness of reflection. I've used it for this application in many production environments with great success. Reflection of course comes with much overhead, but caching the result of the grunt work can eliminate a lot of that overhead. In terms of automatically setting properties, I disagree here as well. Supposing you use generics and pass through an object, you can use reflection to invoke a specific constructor etc. – pim Feb 27 '16 at 19:18
2

We have implemented the following solution and feel it works pretty well. It's pretty simple and requires a bit more wiring up then what a mapper would do. However, sometimes it is nice to have the manual control and honestly, you wire up once and you're done.

In a nutshell: Our domain models implement an interface that has a method that takes in an IDataReader and populates the model properties from it. We then use Generics and Reflection to create an instance of the model and call the Parse method on it.

We considered using a constructor and passing IDataReader to it, but the basic performance checks we did seemed to suggest the interface was consistently faster (if only by a little). Also, the interface route provides instant feedback via compilation errors.

One of the things I like, is that you can utilize private set for properties like Age in the example below and set them straight from the database.

public interface IDataReaderParser
{
    void Parse(IDataReader reader);
}

public class Foo : IDataReaderParser
{
    public string Name { get; set; }
    public int Age { get; private set; }

    public void Parse(IDataReader reader)
    {
        Name = reader["Name"] as string;
        Age = Convert.ToInt32(reader["Age"]);
    }
}

public class DataLoader
{
    public static IEnumerable<TEntity> GetRecords<TEntity>(string connectionStringName, string storedProcedureName, IEnumerable<SqlParameter> parameters = null)
                where TEntity : IDataReaderParser, new()
    {
        using (var sqlCommand = new SqlCommand(storedProcedureName, Connections.GetSqlConnection(connectionStringName)))
        {
            using (sqlCommand.Connection)
            {
                sqlCommand.CommandType = CommandType.StoredProcedure;
                AssignParameters(parameters, sqlCommand);
                sqlCommand.Connection.Open();

                using (var sqlDataReader = sqlCommand.ExecuteReader())
                {
                    while (sqlDataReader.Read())
                    {
                        //Create an instance and parse the reader to set the properties
                        var entity = new TEntity();
                        entity.Parse(sqlDataReader);
                        yield return entity;
                    }
                }
            }
        }
    }
}

To call it, you simply provide the type parameter

IEnumerable<Foo> foos = DataLoader.GetRecords<Foo>(/* params */)
Airn5475
  • 2,452
  • 29
  • 51
  • 1
    i like the solution with Interface. Maybe it would be nice to use new() as where-clause for T. So you don't need any reflection like Activator class. – Sebi Sep 23 '16 at 06:00
  • @Sebi thank you for the great suggestion! Somehow I forgot about that ability with generics! Note that you need to add `new()` as a generic constraint. Thank you for also expressing it kindly and without snark and down votes! – Airn5475 Sep 26 '16 at 14:22
  • @Aim5475 Thanks for your answer but isn't it very similar to this one? http://stackoverflow.com/a/1202973/15928 – Anthony Sep 28 '16 at 10:44
  • 1
    @Anthony I think I agree with you after looking more closely at the 'Update' again, so no disrespect to that solution. IMO, I feel mine is a bit cleaner in that I don't have to pass two functions to the 'GetRecords' method, just a type parameter. #moreThanOneWayToSkinACat – Airn5475 Sep 28 '16 at 12:45
  • 1
    This solution is still valid! Just tried with .NET Core 3.1 and it is much simpler to segregate and reuse both for DB and Code first compared to the first answer with two functions – Philip May 06 '20 at 13:23
  • @Airn5475 Is it possible to integrate async/await task for the same solution? Will there be a benefit? – Philip May 11 '20 at 08:24
2

NOTE: This is .NET Core code

A stupidly performant option, should you not mind an external dependency (the amazing Fast Member nuget package):

public static T ConvertToObject<T>(this SqlDataReader rd) where T : class, new()
{

    Type type = typeof(T);
    var accessor = TypeAccessor.Create(type);
    var members = accessor.GetMembers();
    var t = new T();

    for (int i = 0; i < rd.FieldCount; i++)
    {
        if (!rd.IsDBNull(i))
        {
            string fieldName = rd.GetName(i);

            if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase)))
            {
                accessor[t, fieldName] = rd.GetValue(i);
            }
        }
    }

    return t;
}

To use:

public IEnumerable<T> GetResults<T>(SqlDataReader dr) where T : class, new()
{
    while (dr.Read())
    {
        yield return dr.ConvertToObject<T>());
    }
}
pim
  • 12,019
  • 6
  • 66
  • 69
2

Like Magic

I personally HATE doing manual mapping in constructors, I'm also not a fan of doing my own reflection. So here's another solution courtesy of the wonderful (and fairly ubiquitous) Newtonsoft JSON lib.

It will only work if your property names exactly match the datareader column names, but it worked well for us.

...assumes you've got a datareader name "yourDataReader"...

        var dt = new DataTable();
        dt.Load(yourDataReader);
        // creates a json array of objects
        string json = Newtonsoft.Json.JsonConvert.SerializeObject(dt);
        // this is what you're looking for right??
        List<YourEntityType> list = 
Newtonsoft.Json.JsonConvert
.DeserializeObject<List<YourEntityType>>(json);
C.List
  • 657
  • 8
  • 16
1

Latest versions of C#/.NET5 offer a new awesome feature called "source generators", that I urge everyone to explore.

In a nutshell it allows you to generate C# code at compilation time, the code that will do manual mapping for you. It's obviously 20-30 times faster than any kind of reflection (even with caching and similar speed hacks) - because it literally just assigns properties to IDataReader fields.

There's no short code snippet I could share here, since source generators are still a bit complicated (it should reside in a separate assembly and you will have to learn Roslyn API's etc.) but I do believe it's an awesome feature worth exploring for everyone in the ORM world and around.

I started experimenting with source generators and loving it: https://github.com/jitbit/MapDataReader feel free to "steal" some code from my repo.

disclaimer: I shared a link to my own github repo

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
0

The simplest Solution :

var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();

Then select them in order to map them to any type.

Mohsen
  • 4,000
  • 8
  • 42
  • 73
0

For .NET Core 2.0:

Here is an extension method that works with .NET CORE 2.0 to execute RAW SQL and map results to LIST of arbitrary types:

USAGE:

 var theViewModel = new List();
 string theQuery = @"SELECT * FROM dbo.Something";
 theViewModel = DataSQLHelper.ExecSQL(theQuery,_context);

 using Microsoft.EntityFrameworkCore;
 using System.Data;
 using System.Data.SqlClient;
 using System.Reflection;

public static List ExecSQL(string query, myDBcontext context)
 {
 using (context)
 {
 using (var command = context.Database.GetDbConnection().CreateCommand())
 {
 command.CommandText = query;
 command.CommandType = CommandType.Text;
 context.Database.OpenConnection();
                using (var result = command.ExecuteReader())
                {
                    List<T> list = new List<T>();
                    T obj = default(T);
                    while (result.Read())
                    {
                        obj = Activator.CreateInstance<T>();
                        foreach (PropertyInfo prop in obj.GetType().GetProperties())
                        {
                            if (!object.Equals(result[prop.Name], DBNull.Value))
                            {
                                prop.SetValue(obj, result[prop.Name], null);
                            }
                        }
                        list.Add(obj);
                    }
                    return list;

                }
            }
        }
    }
Tony Bourdeaux
  • 706
  • 6
  • 2
0

My version

Usage:

var Q = await Reader.GetTable<DbRoom>("SELECT id, name FROM rooms");

PgRoom is

public class DbRoom
{
    [Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }
}

Reader.GetTable contains:

            using (var R = await Q.ExecuteReaderAsync())
            {
                List<T> Result = new List<T>();

                Dictionary<int, PropertyInfo> Props = new Dictionary<int, PropertyInfo>();

                foreach (var p in typeof(T).GetProperties())
                {
                    for (int i = 0; i < R.FieldCount; i++)
                    {
                        if (p.GetCustomAttributes<ColumnAttribute>().FirstOrDefault(t => t.Name == R.GetName(i)) != null
                            && p.PropertyType == R.GetFieldType(i))
                        {
                            Props.Add(i, p);
                        }
                    }
                }

                while (await R.ReadAsync())
                {
                    T row = new T();

                    foreach (var kvp in Props)
                    {
                        kvp.Value.SetValue(row, R[kvp.Key]);
                    }

                    Result.Add(row);
                }

                return Result;
            }
-1

I found this solution.

var cmd = ctx.Connection.CreateCommand();

T result = DbDataReaderdHelper.Fill<T>(cmd)

public static class DbDataReaderdHelper
{
    public static List<T> Fill<T>(DbCommand dbCommand) where T : new()
    {
        List<T> result = new List<T>();
        var reader = dbCommand.ExecuteReader();

        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Type type = typeof(T);
                T obj = (T)Activator.CreateInstance(type);
                PropertyInfo[] properties = type.GetProperties();

                foreach (PropertyInfo property in properties)
                {
                    var value = reader[property.Name];

                    try
                    {
                        if (value != null)
                        {
                            var convertedValue = TypeDescriptor.GetConverter(property.PropertyType).ConvertFromInvariantString(value.ToString());

                            property.SetValue(obj, convertedValue);
                        }
                    }
                    catch {}
                }
                result.Add(obj);
            }
        }

        reader.Close();

        return result;
    }
}
ccassob
  • 335
  • 3
  • 10
-1

Please check this answer

Simple, Fast IDataReader to IEnumerable<T> conversion using reflection + lambda expressions

https://stackoverflow.com/a/70321210/3343007

Vaibhav J
  • 1,316
  • 8
  • 15