如何轻松地将DataReader转换为List <T>?

我有一个DataReader中的数据,我想要转换为List<T> 。 什么是可能的简单解决scheme呢?

例如在CustomerEntity类中,我有CustomerId和CustomerName属性。如果我的DataReader将这两列作为数据返回,那么如何将它转换为List<CustomerEntity>

我已经看到在属性或字段上使用Reflection和属性的系统将DataReaders映射到对象。 (有点像LinqToSql所做的那样)。它们节省了一些打字的时间,并且可以在编写DBNull等时减less错误的数量。一旦caching了生成的代码,它们可以更快,然后大多数手写代码也是如此,所以请考虑 “高速公路”,如果你这样做很多。

有关这方面的一个示例,请参阅“.NET中的reflection防御” 。

然后你可以编写像这样的代码

 class CustomerDTO { [Field("id")] public int? CustomerId; [Field("name")] public string CustomerName; } 

 using (DataReader reader = ...) { List<CustomerDTO> customers = reader.AutoMap<CustomerDTO>() .ToList(); } 

(AutoMap(),是一个扩展方法)


@斯蒂尔加,感谢您的好评

如果能够,你可能会更好地使用NHibernate,EF或Linq到Sql等,但在旧的项目(或其他(有时是有效的)原因,例如“这里没有发明”,“爱存储过程”等)使用ORM并不总是可行的,所以一个轻量级的系统可能会有用“袖子”

如果您还需要编写大量的IDataReader循环,您将看到减less编码(和错误)的好处, 而不必更改正在工作的系统的体系结构 。 这并不是说从一开始就是一个好的架构。

我假定CustomerDTO不会离开数据访问层,而复合对象等将由数据访问层使用DTO对象构build。

我会build议为此写一个扩展方法:

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

然后你可以使用LINQ的ToList()方法将它转换成List<T> ,如下所示:

 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(); } 

我实际上build议在Customer (或其他地方)中放置一个FromDataReader方法:

 public static Customer FromDataReader(IDataReader reader) { ... } 

这将离开:

 using (IDataReader reader = ...) { List<Customer> customers = reader.Select<Customer>(Customer.FromDataReader) .ToList(); } 

(我不认为types推断在这种情况下会起作用,但是我可能是错的…)

我用这种情况写了下面的方法。

首先,添加命名空间: System.Reflection

例如: T是返回types(ClassName), dr是映射DataReader参数

C#,像下面这样的调用映射方法:

 List<Person> personList = new List<Person>(); personList = DataReaderMapToList<Person>(dataReaderForPerson); 

这是映射方法:

 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,调用映射方法如下:

 Dim personList As New List(Of Person) personList = DataReaderMapToList(Of Person)(dataReaderForPerson) 

这是映射方法:

 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 

最简单的解决scheme:

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

你不能简单地(直接)将数据读取器转换为列表。

你必须遍历数据logging器中的所有元素并插入到列表中

低于示例代码

 using (drOutput) { System.Collections.Generic.List<CustomerEntity > arrObjects = new System.Collections.Generic.List<CustomerEntity >(); int customerId = drOutput.GetOrdinal("customerId "); int CustomerName = drOutput.GetOrdinal("CustomerName "); while (drOutput.Read()) { CustomerEntity obj=new CustomerEntity (); obj.customerId = (drOutput[customerId ] != Convert.DBNull) ? drOutput[customerId ].ToString() : null; obj.CustomerName = (drOutput[CustomerName ] != Convert.DBNull) ? drOutput[CustomerName ].ToString() : null; arrObjects .Add(obj); } } 

很显然@Ian Ringrose的中心论点是你应该使用一个图书馆这是最好的单一答案(因此+1),但是对于最小的一次性使用或演示代码,这里是@SLaks@Jon Skeet的更细粒度的(+1)答案:

 public List<XXX> Load( <<args>> ) { using ( var connection = CreateConnection() ) using ( var command = Create<<ListXXX>>Command( <<args>>, connection ) ) { connection.Open(); using ( var reader = command.ExecuteReader() ) return reader.Cast<IDataRecord>() .Select( x => new XXX( x.GetString( 0 ), x.GetString( 1 ) ) ) .ToList(); } } 

就像@Jon Skeet的回答一样

  .Select( x => new XXX( x.GetString( 0 ), x.GetString( 1 ) ) ) 

位可以被提取到一个帮手(我喜欢转储他们在查询类):

  public static XXX FromDataRecord( this IDataRecord record) { return new XXX( record.GetString( 0 ), record.GetString( 1 ) ); } 

并用作:

  .Select( FromDataRecord ) 

更新3月9日13:另请参见一些优秀的进一步细微的编码技术,在这个答案拆分样板

我会(并且已经)开始使用Dapper 。 使用你的例子就像(从内存中写的):

 public List<CustomerEntity> GetCustomerList() { using (DbConnection connection = CreateConnection()) { return connection.Query<CustomerEntity>("procToReturnCustomers", commandType: CommandType.StoredProcedure).ToList(); } } 

CreateConnection()将处理访问你的数据库并返回一个连接。

Dapper自动将数据字段映射到属性。 它也支持多种types和结果集,速度非常快。

查询返回IEnumerable因此返回ToList()

我已经在一个宠物项目中介绍了这个。使用你想要的。

请注意,ListEx实现了IDataReader接口。

 people = new ListExCommand(command) .Map(p=> new ContactPerson() { Age = p.GetInt32(p.GetOrdinal("Age")), FirstName = p.GetString(p.GetOrdinal("FirstName")), IdNumber = p.GetInt64(p.GetOrdinal("IdNumber")), Surname = p.GetString(p.GetOrdinal("Surname")), Email = "z.evans@caprisoft.co.za" }) .ToListEx() .Where("FirstName", "Peter"); 

或者像下面的例子那样使用对象映射。

 people = new ListExAutoMap(personList) .Map(p => new ContactPerson() { Age = p.Age, FirstName = p.FirstName, IdNumber = p.IdNumber, Surname = p.Surname, Email = "z.evans@caprisoft.co.za" }) .ToListEx() .Where(contactPerson => contactPerson.FirstName == "Zack"); 

看看http://caprisoft.codeplex.com

我知道这个问题很老,已经回答了,但是…

由于SqlDataReader已经实现了IEnumerable,为什么需要在logging上创build一个循环呢?

我一直在使用下面的方法没有任何问题,也没有任何性能问题:到目前为止,我已经用IList,List(Of T),IEnumerable,IEnumerable(Of T),IQueryable和IQueryable(Of T)

 Imports System.Data.SqlClient Imports System.Data Imports System.Threading.Tasks Public Class DataAccess Implements IDisposable #Region " Properties " ''' <summary> ''' Set the Query Type ''' </summary> ''' <value></value> ''' <remarks></remarks> Public WriteOnly Property QueryType() As CmdType Set(ByVal value As CmdType) _QT = value End Set End Property Private _QT As CmdType ''' <summary> ''' Set the query to run ''' </summary> ''' <value></value> ''' <remarks></remarks> Public WriteOnly Property Query() As String Set(ByVal value As String) _Qry = value End Set End Property Private _Qry As String ''' <summary> ''' Set the parameter names ''' </summary> ''' <value></value> ''' <remarks></remarks> Public WriteOnly Property ParameterNames() As Object Set(ByVal value As Object) _PNs = value End Set End Property Private _PNs As Object ''' <summary> ''' Set the parameter values ''' </summary> ''' <value></value> ''' <remarks></remarks> Public WriteOnly Property ParameterValues() As Object Set(ByVal value As Object) _PVs = value End Set End Property Private _PVs As Object ''' <summary> ''' Set the parameter data type ''' </summary> ''' <value></value> ''' <remarks></remarks> Public WriteOnly Property ParameterDataTypes() As DataType() Set(ByVal value As DataType()) _DTs = value End Set End Property Private _DTs As DataType() ''' <summary> ''' Check if there are parameters, before setting them ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Private ReadOnly Property AreParams() As Boolean Get If (IsArray(_PVs) And IsArray(_PNs)) Then If (_PVs.GetUpperBound(0) = _PNs.GetUpperBound(0)) Then Return True Else Return False End If Else Return False End If End Get End Property ''' <summary> ''' Set our dynamic connection string ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Private ReadOnly Property _ConnString() As String Get If System.Diagnostics.Debugger.IsAttached OrElse My.Settings.AttachToBeta OrElse Not (Common.CheckPaid) Then Return My.Settings.DevConnString Else Return My.Settings.TurboKitsv2ConnectionString End If End Get End Property Private _Rdr As SqlDataReader Private _Conn As SqlConnection Private _Cmd As SqlCommand #End Region #Region " Methods " ''' <summary> ''' Fire us up! ''' </summary> ''' <remarks></remarks> Public Sub New() Parallel.Invoke(Sub() _Conn = New SqlConnection(_ConnString) End Sub, Sub() _Cmd = New SqlCommand End Sub) End Sub ''' <summary> ''' Get our results ''' </summary> ''' <returns></returns> ''' <remarks></remarks> Public Function GetResults() As SqlDataReader Try Parallel.Invoke(Sub() If AreParams Then PrepareParams(_Cmd) End If _Cmd.Connection = _Conn _Cmd.CommandType = _QT _Cmd.CommandText = _Qry _Cmd.Connection.Open() _Rdr = _Cmd.ExecuteReader(CommandBehavior.CloseConnection) End Sub) If _Rdr.HasRows Then Return _Rdr Else Return Nothing End If Catch sEx As SqlException Return Nothing Catch ex As Exception Return Nothing End Try End Function ''' <summary> ''' Prepare our parameters ''' </summary> ''' <param name="objCmd"></param> ''' <remarks></remarks> Private Sub PrepareParams(ByVal objCmd As Object) Try Dim _DataSize As Long Dim _PCt As Integer = _PVs.GetUpperBound(0) For i As Long = 0 To _PCt If IsArray(_DTs) Then Select Case _DTs(i) Case 0, 33, 6, 9, 13, 19 _DataSize = 8 Case 1, 3, 7, 10, 12, 21, 22, 23, 25 _DataSize = Len(_PVs(i)) Case 2, 20 _DataSize = 1 Case 5 _DataSize = 17 Case 8, 17, 15 _DataSize = 4 Case 14 _DataSize = 16 Case 31 _DataSize = 3 Case 32 _DataSize = 5 Case 16 _DataSize = 2 Case 15 End Select objCmd.Parameters.Add(_PNs(i), _DTs(i), _DataSize).Value = _PVs(i) Else objCmd.Parameters.AddWithValue(_PNs(i), _PVs(i)) End If Next Catch ex As Exception End Try End Sub #End Region #Region "IDisposable Support" Private disposedValue As Boolean ' To detect redundant calls ' IDisposable Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then End If Try Erase _PNs : Erase _PVs : Erase _DTs _Qry = String.Empty _Rdr.Close() _Rdr.Dispose() _Cmd.Parameters.Clear() _Cmd.Connection.Close() _Conn.Close() _Cmd.Dispose() _Conn.Dispose() Catch ex As Exception End Try End If Me.disposedValue = True End Sub ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources. Protected Overrides Sub Finalize() ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. Dispose(False) MyBase.Finalize() End Sub ' This code added by Visual Basic to correctly implement the disposable pattern. Public Sub Dispose() Implements IDisposable.Dispose ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region End Class 

强打字class

 Public Class OrderDCTyping Public Property OrderID As Long = 0 Public Property OrderTrackingNumber As String = String.Empty Public Property OrderShipped As Boolean = False Public Property OrderShippedOn As Date = Nothing Public Property OrderPaid As Boolean = False Public Property OrderPaidOn As Date = Nothing Public Property TransactionID As String End Class 

用法

 Public Function GetCurrentOrders() As IEnumerable(Of OrderDCTyping) Try Using db As New DataAccess With db .QueryType = CmdType.StoredProcedure .Query = "[Desktop].[CurrentOrders]" Using _Results = .GetResults() If _Results IsNot Nothing Then _Qry = (From row In _Results.Cast(Of DbDataRecord)() Select New OrderDCTyping() With { .OrderID = Common.IsNull(Of Long)(row, 0, 0), .OrderTrackingNumber = Common.IsNull(Of String)(row, 1, String.Empty), .OrderShipped = Common.IsNull(Of Boolean)(row, 2, False), .OrderShippedOn = Common.IsNull(Of Date)(row, 3, Nothing), .OrderPaid = Common.IsNull(Of Boolean)(row, 4, False), .OrderPaidOn = Common.IsNull(Of Date)(row, 5, Nothing), .TransactionID = Common.IsNull(Of String)(row, 6, String.Empty) }).ToList() Else _Qry = Nothing End If End Using Return _Qry End With End Using Catch ex As Exception Return Nothing End Try End Function