Como posso converter facilmente DataReader para List ?

Eu tenho dados em um DataReader que eu quero ser convertido em uma List . O que é uma solução simples possível para isso?

Por exemplo, na class CustomerEntity, tenho propriedades CustomerId e CustomerName. Se meu DataReader retornar essas duas colunas como dados, como posso convertê-las em List .

    Eu vi sistemas que usam Reflexão e atributos em Propriedades ou campos para mapear DataReaders para objects. (Um pouco como o LinqToSql faz.) Eles economizam um pouco de digitação e podem reduzir o número de erros ao codificar DBNull etc. Uma vez que você armazena em cache o código gerado, eles podem ser mais rápidos que a maioria dos códigos escritos manualmente. “Estrada alta” se você está fazendo muito isso.

    Veja “A Defense of Reflection in .NET” para um exemplo disso.

    Você pode então escrever código como

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

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

    (AutoMap (), é um método de extensão)


    @Stilgar, obrigado por um ótimo comentário

    Se você é capaz de usar o NHibernate, EF ou Linq para Sql, etc No entanto, no projeto antigo (ou por outras razões (por vezes válidas), por exemplo, “não inventado aqui”, “amor por procs armazenados” etc) Nem sempre é possível usar um ORM, portanto, um sistema mais leve pode ser útil para ter “mangas para cima”

    Se todo o necessário também escrever muitos loops IDataReader, você verá o benefício de reduzir a codificação (e erros) sem ter que alterar a arquitetura do sistema em que você está trabalhando. Isso não quer dizer que é uma boa arquitetura para começar.

    Estou assumindo que o CustomerDTO não sairá da camada de access a dados e que os objects compostos, etc., serão construídos pela camada de access a dados usando os objects DTO.

    Eu sugeriria escrever um método de extensão para isso:

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

    Você pode usar o método ToList() do LINQ para convertê-lo em uma List se desejar, assim:

     using (IDataReader reader = ...) { List 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(); } 

    Eu realmente sugiro colocar um método FromDataReader no Customer (ou em outro lugar):

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

    Isso deixaria:

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

    (Eu não acho que a inferência de tipos funcionaria neste caso, mas posso estar errado …)

    Eu escrevi o seguinte método usando este caso.

    Primeiro, adicione o namespace: System.Reflection

    Por exemplo: T é o tipo de retorno (ClassName) e dr é o parâmetro para mapear o DataReader

    C #, método de mapeamento de chamadas como o seguinte:

     List personList = new List(); personList = DataReaderMapToList(dataReaderForPerson); 

    Este é o método de mapeamento:

     public static List DataReaderMapToList(IDataReader dr) { List list = new List(); T obj = default(T); while (dr.Read()) { obj = Activator.CreateInstance(); 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, método de mapeamento de chamadas como o seguinte:

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

    Este é o método de mapeamento:

     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 

    A solução mais simples:

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

    Eu teria (e teria) começado a usar o Dapper . Para usar o seu exemplo seria como (escrito da memory):

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

    CreateConnection() trataria de acessar seu database e retornar uma conexão.

    O Dapper manipula campos de dados para propriedades automaticamente. Também suporta vários tipos e conjuntos de resultados e é muito rápido.

    Consulta retorna IEnumerable portanto, o ToList() .

    Você não pode simplesmente (diretamente) converter o datareader para lista.

    Você tem que percorrer todos os elementos no datareader e inserir na lista

    abaixo do código de amostra

     using (drOutput) { System.Collections.Generic.List arrObjects = new System.Collections.Generic.List(); 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); } } 

    Obviamente a tese central do @Ian Ringrose que você deveria estar usando uma biblioteca para isso é a melhor resposta aqui (daí um +1), mas para um código descartável ou demo mínimo aqui está uma ilustração concreta do comentário sutil de @SLaks em @Jon Skeet resposta mais granular de @Jon Skeet (+1):

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

    Como na @Jon Skeet , o

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

    bit pode ser extraído em um ajudante (eu gosto de despejo-los na class de consulta):

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

    e usado como:

      .Select( FromDataRecord ) 

    ATUALIZAÇÃO 13 de março 13: Veja também Algumas excelentes técnicas sutis de codificação para dividir o clichê nesta resposta

    Eu cobri isso em um projeto de estimação .. use o que quiser.

    Observe que o ListEx implementa a interface 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"); 

    Ou use o mapeamento de objects como no exemplo a seguir.

     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"); 

    Dê uma olhada no http://caprisoft.codeplex.com

    Eu sei que esta pergunta é antiga, e já respondi, mas …

    Como SqlDataReader já implementa IEnumerable, por que há a necessidade de criar um loop sobre os registros?

    Eu tenho usado o método abaixo sem problemas, nem sem problemas de desempenho: Até agora eu testei com IList, lista (de T), IEnumerable, IEnumerable (de T), IQueryable e IQueryable (de T)

     Imports System.Data.SqlClient Imports System.Data Imports System.Threading.Tasks Public Class DataAccess Implements IDisposable #Region " Properties " '''  ''' Set the Query Type '''  '''  '''  Public WriteOnly Property QueryType() As CmdType Set(ByVal value As CmdType) _QT = value End Set End Property Private _QT As CmdType '''  ''' Set the query to run '''  '''  '''  Public WriteOnly Property Query() As String Set(ByVal value As String) _Qry = value End Set End Property Private _Qry As String '''  ''' Set the parameter names '''  '''  '''  Public WriteOnly Property ParameterNames() As Object Set(ByVal value As Object) _PNs = value End Set End Property Private _PNs As Object '''  ''' Set the parameter values '''  '''  '''  Public WriteOnly Property ParameterValues() As Object Set(ByVal value As Object) _PVs = value End Set End Property Private _PVs As Object '''  ''' Set the parameter data type '''  '''  '''  Public WriteOnly Property ParameterDataTypes() As DataType() Set(ByVal value As DataType()) _DTs = value End Set End Property Private _DTs As DataType() '''  ''' Check if there are parameters, before setting them '''  '''  '''  '''  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 '''  ''' Set our dynamic connection string '''  '''  '''  '''  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 " '''  ''' Fire us up! '''  '''  Public Sub New() Parallel.Invoke(Sub() _Conn = New SqlConnection(_ConnString) End Sub, Sub() _Cmd = New SqlCommand End Sub) End Sub '''  ''' Get our results '''  '''  '''  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 '''  ''' Prepare our parameters '''  '''  '''  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 

    Classe de digitação forte

     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 

    Uso

     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