c# - llenar - ¿Cómo puedo convertir fácilmente DataReader a List<T>?
llenar lista con datareader c# (9)
Esta pregunta ya tiene una respuesta aquí:
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();