c# - Comment puis-je facilement convertir DataReader en List<T>?




generic-list (9)

Je sais que cette question est ancienne, et a 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 sans problèmes 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

Usage

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

J'ai des données dans un DataReader que je veux convertir en une List<T> . Qu'est-ce qu'une solution simple possible pour cela?

Par exemple dans la classe CustomerEntity, j'ai les propriétés CustomerId et CustomerName. Si mon DataReader renvoie ces deux colonnes en tant que données, comment puis-je le convertir en List<CustomerEntity> .


Je suggère d'écrire une méthode d'extension pour cela:

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

Vous pouvez ensuite utiliser la méthode ToList() de LINQ pour la convertir en List<T> si vous le souhaitez, 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ère en fait de mettre une méthode FromDataReader dans Customer (ou ailleurs):

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

Cela laisserait:

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

(Je ne pense pas que l' inférence de type fonctionnerait dans ce cas, mais je pourrais me tromper ...)


J'ai couvert ceci dans un projet d'animal de compagnie. Employez ce que vous voulez.

Notez que 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 = "[email protected]"
})
.ToListEx()
.Where("FirstName", "Peter");

Ou utilisez le mappage d'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 = "[email protected]"
})
.ToListEx()
.Where(contactPerson => contactPerson.FirstName == "Zack");

Jetez un oeil à http://caprisoft.codeplex.com


La solution la plus simple:

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

J'ai vu des systèmes qui utilisent Reflection et des attributs sur des propriétés ou des champs pour mapper des DataReaders à des objets. (Un peu comme LinqToSql.) Ils économisent un peu de frappe 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 des codes manuscrits. "High road" si vous le faites beaucoup.

Voir "Une défense de la réflexion dans .NET" pour un exemple de ceci.

Vous pouvez ensuite écrire du 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 super commentaire

Si vous êtes capable de mieux utiliser NHibernate, EF ou Linq à Sql, etc Cependant sur l'ancien projet (ou pour d'autres raisons (parfois valables), par exemple "pas inventé ici", "amour des process stockés" etc) Il n'est pas toujours possible d'utiliser un ORM, donc un système de poids plus léger peut être utile pour avoir "les manches"

Si vous avez également besoin d'écrire beaucoup de boucles IDataReader, vous verrez l'avantage de réduire le codage (et les erreurs) sans avoir à modifier 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 que les objets composites, etc., seront construits par la couche d'accès aux données en utilisant les objets DTO.


Evidemment, la thèse centrale de @Ian Ringrose selon laquelle vous devriez utiliser une bibliothèque pour cela est la meilleure réponse ici (d'où +1), mais pour un code minimal de démontage ou de démonstration, voici une illustration concrète du commentaire subtil de @SLaks sur @Jon Skeet réponse plus granulaire 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();
    }
}

Comme dans la @Jon Skeet de @Jon Skeet , le

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

bit peut être extrait dans une aide (j'aime les vider dans la classe de requête):

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

et utilisé comme:

            .Select( FromDataRecord )

MISE À JOUR 9 mars 13: Voir aussi Quelques excellentes techniques de codage subtiles pour séparer le passe-partout de cette réponse


Je voudrais (et j'ai) commencé à utiliser Dapper . Pour utiliser votre exemple serait comme (écrit à partir de la mémoire):

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

CreateConnection() accès à votre base de données et renvoyer une connexion.

Dapper gère la mise en correspondance automatique des champs de données avec les propriétés. Il prend également en charge plusieurs types et ensembles de résultats et est très rapide.

La requête retourne IEnumerable où le ToList() .


J'ai écrit la méthode suivante en utilisant ce cas.

Tout d'abord, ajoutez l'espace de noms: System.Reflection

Par exemple: T est le type de retour (ClassName) et dr est un paramètre pour mapper DataReader

C #, méthode de mappage d'appel comme suit:

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

C'est la méthode de cartographie:

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 mappage d'appel comme suit:

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

C'est la méthode de cartographie:

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

Le faire sans Linq comme vous l'avez dit:

public class Order : IComparable
{
    public DateTime OrderDate { get; set; }
    public int OrderId { get; set; }

    public int CompareTo(object obj)
    {
        Order orderToCompare = obj as Order;
        if (orderToCompare.OrderDate < OrderDate || orderToCompare.OrderId < OrderId)
        {
            return 1;
        }
        if (orderToCompare.OrderDate > OrderDate || orderToCompare.OrderId > OrderId)
        {
            return -1;
        }

        // The orders are equivalent.
        return 0;
    }
}

Ensuite, appelez .sort () sur votre liste de commandes





c# datareader generic-list