net llenar lista dbdatareader data convert con c# datareader generic-list

c# - llenar - ¿Cómo puedo convertir fácilmente DataReader a List<T>?



llenar lista con datareader c# (9)

Tengo datos en un DataReader que quiero convertir a una List<T> . ¿Cuál es una posible solución simple para esto?

Por ejemplo, en la clase CustomerEntity, tengo las propiedades CustomerId y CustomerName. Si mi DataReader devuelve estas dos columnas como datos, ¿cómo puedo convertirlo en List<CustomerEntity> ?


He escrito el siguiente método usando este caso.

Primero, agregue el espacio de nombres: System.Reflection

Por ejemplo: T es el tipo de retorno (ClassName) y dr es un parámetro para mapear DataReader

C #, método de asignación de llamadas como el siguiente:

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

Este es el método de mapeo:

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, método de asignación de llamadas como el siguiente:

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

Este es el método de mapeo:

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


He visto sistemas que usan Reflexión y atributos en Propiedades o campos para mapear DataReaders a objetos. (Algo así como lo que LinqToSql hace). Ahorran un poco de tipeo y pueden reducir el número de errores al codificar DBNull, etc. Una vez que almacena en caché el código generado, pueden ser más rápidos que la mayoría del código escrito a mano, así que considere el "High road" si estás haciendo esto mucho.

Consulte "Una defensa de la reflexión en .NET" para ver un ejemplo de esto.

A continuación, puede escribir código como

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

...

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

(AutoMap (), es un método de extensión)

@Stilgar, gracias por un gran comentario

Si eres capaz de utilizar mejor NHibernate, EF o Linq a Sql, etc. Sin embargo, en el proyecto anterior (o por otros motivos (a veces válidos), por ejemplo, "no inventado aquí", "amor por procs almacenados", etc.) No siempre es posible usar un ORM, por lo que un sistema más liviano puede ser útil para "subirse las mangas"

Si también necesita escribir muchos bucles IDataReader, verá el beneficio de reducir la codificación (y los errores) sin tener que cambiar la arquitectura del sistema en el que está trabajando. Eso no quiere decir que sea una buena arquitectura para empezar ...

Supongo que CustomerDTO no saldrá de la capa de acceso a datos y los objetos compuestos, etc., serán creados por la capa de acceso a datos utilizando los objetos DTO.


Lo he cubierto en un proyecto para mascotas ... usa lo que quieras.

Tenga en cuenta que ListEx implementa la interfaz 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 = "[email protected]" }) .ToListEx() .Where("FirstName", "Peter");

O use mapeo de objetos como en el siguiente ejemplo.

people = new ListExAutoMap(personList) .Map(p => new ContactPerson() { Age = p.Age, FirstName = p.FirstName, IdNumber = p.IdNumber, Surname = p.Surname, Email = "[email protected]" }) .ToListEx() .Where(contactPerson => contactPerson.FirstName == "Zack");

Eche un vistazo a http://caprisoft.codeplex.com


Obviamente, la tesis central de @Ian Ringrose que deberías usar una biblioteca para esto es la mejor respuesta individual aquí (de ahí un +1), pero para un código mínimo o de demostración aquí hay una ilustración concreta del sutil comentario de @SLaks en @Jon Skeet respuesta más granular de @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(); } }

Como en la @Jon Skeet de @Jon Skeet , el

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

bit se puede extraer en un helper (me gusta volcarlos en la clase de consulta):

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

y usado como:

.Select( FromDataRecord )

ACTUALIZACIÓN, 9 de marzo, 13: Vea también algunas técnicas de codificación sutiles y excelentes para dividir el texto estándar en esta respuesta


Sé que esta pregunta es vieja, y ya respondida, pero ...

Como SqlDataReader ya implementa IEnumerable, ¿por qué hay una necesidad de crear un bucle sobre los registros?

He estado usando el método a continuación sin ningún problema, ni ningún problema de rendimiento: hasta ahora he probado con IList, List (Of T), IEnumerable, IEnumerable (Of T), IQueryable, and 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

Clase fuerte de escritura

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


Simplemente no puede (directamente) convertir el lector de datos a la lista.

Tienes que recorrer todos los elementos en el lector de datos e insertarlos en la lista

debajo del código de muestra

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


Sugeriría escribir un método de extensión para esto:

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

Luego puede usar el método ToList() LINQ para convertir eso en una List<T> si lo desea, así:

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

En realidad, sugeriría poner un método FromDataReader en Customer (o en otro lugar):

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

Eso se iría:

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

(No creo que la inferencia de tipo funcionaría en este caso, pero podría estar equivocado ...)


Yo (y he) comenzado a usar Dapper . Para usar tu ejemplo sería como (escrito desde la memoria):

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

CreateConnection() manejaría el acceso a su base de datos y devolver una conexión.

Dapper maneja los campos de datos de mapeo a las propiedades de forma automática. También admite múltiples tipos y conjuntos de resultados y es muy rápido.

La consulta devuelve IEnumerable por lo tanto, ToList() .


La solución más simple:

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