Comment puis-je convertir facilement DataReader en liste? [dupliquer]
cette question a déjà une réponse ici:
- comment améliorer la couche d'accès aux données select method Pattern 7 réponses
- Convertir les lignes d'un lecteur de données en résultats dactylographiés 7 réponses
j'ai des données dans un DataReader
que je veux convertir en un List<T>
.
Quelle est la solution la plus simple?
pour par exemple dans la classe CustomerEntity, j'ai les propriétés CustomerId et CustomerName.Si mon DataReader retourne ces deux colonnes en tant que données, alors comment puis-je les convertir en List<CustomerEntity>
.
9 réponses
j'ai vu des systèmes qui utilisent la réflexion et les attributs sur les propriétés ou les champs pour cartographier les lecteurs de données aux objets. (Un peu comme ce que fait LinqToSql.) Ils économisent un peu de Dactylographie et peuvent réduire le nombre d'erreurs lors du codage pour DBNull etc. Une fois que vous mettez en cache le code généré, ils peuvent être plus rapides que la plupart du code écrit à la main ainsi, alors faites considérer la "route haute" si vous faites cela beaucoup.
Voir "Une Défense de la Réflexion dans .NET" pour un exemple de cette.
vous pouvez alors écrire le code comme
class CustomerDTO
{
[Field("id")]
public int? CustomerId;
[Field("name")]
public string CustomerName;
}
...
using (DataReader reader = ...)
{
List<CustomerDTO> customers = reader.AutoMap<CustomerDTO>()
.ToList();
}
(AutoMap(), est une méthode d'extension)
@Stilgar, merci pour un grand commentaire
Si pouvoir vous êtes probablement mieux d'utiliser NHibernate, EF ou Linq to Sql, etc cependant sur le vieux projet (ou pour d'autres raisons (parfois valables), par exemple "pas inventé ici", "amour des procs stockés" etc) Il n'est pas toujours possible d'utiliser un ORM, de sorte qu'un système de poids plus léger peut être utile d'avoir "dans vos manches
si vous avez besoin aussi d'écrire beaucoup de boucles IDataReader, vous verrez l'avantage de réduire le codage (et les erreurs) sans avoir à changer l'architecture du système sur lequel vous travaillez. Cela ne veut pas dire que c'est une bonne architecture pour commencer..
je suppose que CustomerDTO ne sortira pas de la couche d'accès aux données et les objets composites etc seront construits par la couche d'accès aux données en utilisant les objets DTO.
je suggère d'écrire une méthode d'extension pour ceci:
public static IEnumerable<T> Select<T>(this IDataReader reader,
Func<IDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
}
vous pouvez alors utiliser la méthode ToList()
de LINQ pour transformer cela en List<T>
si vous voulez, comme ceci:
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();
}
je suggérerais en fait de mettre une méthode FromDataReader
dans Customer
(ou ailleurs):
public static Customer FromDataReader(IDataReader reader) { ... }
Qui laisserait:
using (IDataReader reader = ...)
{
List<Customer> customers = reader.Select<Customer>(Customer.FromDataReader)
.ToList();
}
(je n'ai pas penser l'inférence de type fonctionnerait dans ce cas, mais je pourrais me tromper...)
j'ai écrit la méthode suivante en utilisant ce cas.
tout d'abord, ajouter l'espace de nom: System.Reflection
par exemple: T
est le type de retour (Nom de classe) et dr
est le paramètre de mappage DataReader
C#, Appelez la méthode de mapping comme suit:
List<Person> personList = new List<Person>();
personList = DataReaderMapToList<Person>(dataReaderForPerson);
C'est la méthode de cartographie de l':
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éthode de cartographie des appels comme la
Dim personList As New List(Of Person)
personList = DataReaderMapToList(Of Person)(dataReaderForPerson)
C'est la méthode de cartographie de l':
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
la Solution la plus simple:
var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();
je voudrais (et avoir) a commencé à utiliser des Dapper . Pour utiliser votre exemple serait comme (écrit de mémoire):
public List<CustomerEntity> GetCustomerList()
{
using (DbConnection connection = CreateConnection())
{
return connection.Query<CustomerEntity>("procToReturnCustomers", commandType: CommandType.StoredProcedure).ToList();
}
}
CreateConnection()
gérerait l'accès à votre base de données et le retour d'une connexion.
Dapper gère automatiquement les champs de données cartographiques vers les propriétés. Il prend également en charge plusieurs types et ensembles de résultats et est très rapide.
retourne IEnumerable
d'où le ToList()
.
vous ne pouvez pas simplement (directement) convertir le lecteur de données en liste.
vous devez boucler tous les éléments de datareader et insérer dans la liste
sous le code d'échantillon
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);
}
}
évidemment @Ian Ringrose
's thèse centrale que vous devriez utiliser une bibliothèque pour ce est la meilleure réponse unique ici( d'où a +1), mais pour le minimum throwaway ou code de démonstration voici une illustration concrète de @SLaks
's commentaire subtil sur @Jon Skeet
'S réponse plus granulaire (+1'd):
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();
}
}
, Comme dans @Jon Skeet
's réponse, le
.Select( x => new XXX( x.GetString( 0 ), x.GetString( 1 ) ) )
bit peut être extrait dans un helper (j'aime les jeter dans la classe query):
public static XXX FromDataRecord( this IDataRecord record)
{
return new XXX( record.GetString( 0 ), record.GetString( 1 ) );
}
et utilisé comme:
.Select( FromDataRecord )
mise à JOUR Mar 9 13: Voir aussi de l'excellent plus subtiles techniques de codage pour séparer le passe-partout de cette réponse
j'ai couvert ceci dans un projet de compagnie.. utilisez ce que vous voulez.
notez que le ListEx implémente L'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 utilisez la cartographie objet comme dans l'exemple suivant.
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");
regardez http://caprisoft.codeplex.com
je sais que cette question est ancienne, et déjà répondu, mais...
puisque SqlDataReader implémente déjà IEnumerable, pourquoi est-il nécessaire de créer une boucle sur les enregistrements?
j'ai utilisé la méthode ci-dessous sans aucun problème, ni aucun problème de performance: jusqu'à présent j'ai testé avec IList, List (Of T), IEnumerable, IEnumerable(Of T), IQueryable, et 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
Classe De Frappe 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
Utilisation
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