如何轻松地将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");
我知道这个问题很老,已经回答了,但是…
由于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