How can I easily convert DataReader to List<T>? [duplicate]
I have data in a DataReader
which I want to be converted to a List<T>
.
What is a possible simple solution for this?
For e.g. in CustomerEntity class, I have CustomerId and CustomerName properties.If my DataReader returns these two columns as data, then how can I convert it into List<CustomerEntity>
.
Solution 1:
I would suggest writing an extension method for this:
public static IEnumerable<T> Select<T>(this IDataReader reader,
Func<IDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
}
You can then use LINQ's ToList()
method to convert that into a List<T>
if you want, like this:
using (IDataReader reader = ...)
{
List<Customer> customers = reader.Select(r => new Customer {
CustomerId = r["id"] is DBNull ? null : r["id"].ToString(),
CustomerName = r["name"] is DBNull ? null : r["name"].ToString()
}).ToList();
}
I would actually suggest putting a FromDataReader
method in Customer
(or somewhere else):
public static Customer FromDataReader(IDataReader reader) { ... }
That would leave:
using (IDataReader reader = ...)
{
List<Customer> customers = reader.Select<Customer>(Customer.FromDataReader)
.ToList();
}
(I don't think type inference would work in this case, but I could be wrong...)
Solution 2:
I have written the following method using this case.
First, add the namespace: System.Reflection
For Example: T
is return type(ClassName) and dr
is parameter to mapping DataReader
C#, Call mapping method like the following:
List<Person> personList = new List<Person>();
personList = DataReaderMapToList<Person>(dataReaderForPerson);
This is the mapping method:
public static List<T> DataReaderMapToList<T>(IDataReader dr)
{
List<T> list = new List<T>();
T obj = default(T);
while (dr.Read()) {
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties()) {
if (!object.Equals(dr[prop.Name], DBNull.Value)) {
prop.SetValue(obj, dr[prop.Name], null);
}
}
list.Add(obj);
}
return list;
}
VB.NET, Call mapping method like the following:
Dim personList As New List(Of Person)
personList = DataReaderMapToList(Of Person)(dataReaderForPerson)
This is the mapping method:
Public Shared Function DataReaderMapToList(Of T)(ByVal dr As IDataReader) As List(Of T)
Dim list As New List(Of T)
Dim obj As T
While dr.Read()
obj = Activator.CreateInstance(Of T)()
For Each prop As PropertyInfo In obj.GetType().GetProperties()
If Not Object.Equals(dr(prop.Name), DBNull.Value) Then
prop.SetValue(obj, dr(prop.Name), Nothing)
End If
Next
list.Add(obj)
End While
Return list
End Function
Solution 3:
I have seen systems that use Reflection and attributes on Properties or fields to maps DataReaders to objects. (A bit like what LinqToSql does.) They save a bit of typing and may reduce the number of errors when coding for DBNull etc. Once you cache the generated code they can be faster then most hand written code as well, so do consider the “high road” if you are doing this a lot.
See "A Defense of Reflection in .NET" for one example of this.
You can then write code like
class CustomerDTO
{
[Field("id")]
public int? CustomerId;
[Field("name")]
public string CustomerName;
}
...
using (DataReader reader = ...)
{
List<CustomerDTO> customers = reader.AutoMap<CustomerDTO>()
.ToList();
}
(AutoMap(), is an extension method)
@Stilgar, thanks for a great comment
If are able to you are likely to be better of using NHibernate, EF or Linq to Sql, etc However on old project (or for other (sometimes valid) reasons, e.g. “not invented here”, “love of stored procs” etc) It is not always possible to use a ORM, so a lighter weight system can be useful to have “up your sleeves”
If you every needed too write lots of IDataReader loops, you will see the benefit of reducing the coding (and errors) without having to change the architecture of the system you are working on. That is not to say it’s a good architecture to start with..
I am assuming that CustomerDTO will not get out of the data access layer and composite objects etc will be built up by the data access layer using the DTO objects.
A few years after I wrote this answer Dapper entered the world of .NET, it is likely to be a very good starting point for writing your onw AutoMapper, maybe it will completely remove the need for you to do so.
Solution 4:
The simplest Solution :
var dt = new DataTable();
dt.Load(myDataReader);
List<DataRow> rows = dt.AsEnumerable();
var customers = rows.Select(dr=>new Customer(...)).ToList();
Solution 5:
I would (and have) started to use Dapper. To use your example would be like (written from memory):
public List<CustomerEntity> GetCustomerList()
{
using (DbConnection connection = CreateConnection())
{
return connection.Query<CustomerEntity>("procToReturnCustomers", commandType: CommandType.StoredProcedure).ToList();
}
}
CreateConnection()
would handle accessing your db and returning a connection.
Dapper handles mapping datafields to properties automatically. It also supports multiple types and result sets and is very fast.
Query returns IEnumerable
hence the ToList()
.