web-dev-qa-db-ja.com

DataReaderをList <T>に簡単に変換するにはどうすればよいですか?

DataReaderにデータがあり、List<T>に変換したい。これの可能な簡単な解決策は何ですか?

たとえばCustomerEntityクラスには、CustomerIdプロパティとCustomerNameプロパティがあります。DataReaderがこれら2つの列をデータとして返す場合、どのようにList<CustomerEntity>に変換できますか。

108
Laxman

プロパティまたはフィールドのリフレクションと属性を使用して、DataReaderをオブジェクトにマップするシステムを見てきました。 (LinqToSqlの機能に少し似ています。)入力を少し節約し、DBNullなどのコーディング時のエラーの数を減らすことができます。生成されたコードをキャッシュすると、ほとんどの手書きコードよりも速くなりますconsiderこれを頻繁に行う場合は「高速道路」。

この一例については、 「。NETでの反射の防御」 を参照してください。

その後、次のようなコードを書くことができます

class CustomerDTO  
{
    [Field("id")]
    public int? CustomerId;

    [Field("name")]
    public string CustomerName;
}

...

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

(AutoMap()は拡張メソッドです)


@Stilgar、greatコメントをありがとう

toの場合、 NHibernate、EF、またはLinq to Sqlなど を使用する方が適切である可能性が高い(時には有効)理由、たとえば「ここで発明されていない」、「ストアドプロシージャが大好き」など)ORMを常に使用できるとは限らないので、軽量システムは「袖を上げる」のに便利です。

必要以上に多くのIDataReaderループを作成する必要がある場合、コーディング(およびエラー)を削減する利点があります。作業中のシステムのアーキテクチャを変更する必要はありません。に。それは、それが最初から良いアーキテクチャだと言うことではありません。

CustomerDTOはデータアクセスレイヤーから抜け出せず、DTOオブジェクトを使用してデータアクセスレイヤーによって複合オブジェクトなどが構築されると想定しています。

48
Ian Ringrose

このための拡張メソッドを書くことをお勧めします。

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

その後、必要に応じて、LINQのToList()メソッドを使用して、これをList<T>に変換できます。

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

実際にFromDataReaderメソッドをCustomer(または他の場所)に配置することをお勧めします。

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

それは去ります:

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

(私は思考この場合、型推論は機能しませんが、間違っているかもしれません...)

190
Jon Skeet

この場合を使用して、次のメソッドを作成しました。

最初に、名前空間を追加します:System.Reflection

例:Tは戻り値の型(ClassName)であり、drはマッピングDataReaderのパラメーターです

C#、次のようなコールマッピングメソッド:

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

これはマッピング方法です:

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、次のようなコールマッピングメソッド:

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

これはマッピング方法です:

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
59
Ali YALÇIN

最も簡単なソリューション:

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

私は Dapper を使い始めました(そして持っていました)。あなたの例を使用するには、次のようになります(メモリから書き込まれます):

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

CreateConnection()は、データベースへのアクセスと接続の返送を処理します。

Dapperは、データフィールドからプロパティへのマッピングを自動的に処理します。また、複数のタイプと結果セットをサポートし、非常に高速です。

クエリはIEnumerableを返し、したがってToList()を返します。

10
Phil Cooper

データリーダーをリストに単純に(直接)変換することはできません。

あなたはdatareaderのすべての要素をループしてリストに挿入する必要があります

サンプルコードの下

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

}
9
RameshVel

明らかに、これにライブラリを使用する必要があるという@Ian Ringroseの中心的なテーゼがここでの最良の単一の回答です(したがって+1)が、最小限のスローアウェイまたはデモコードについては、@SLaks@Jon Skeetのより詳細な(+ 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();
    }
}

@Jon Skeetの答えのように、

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

ビットはヘルパーに抽出できます(クエリクラスにダンプするのが好きです):

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

および使用:

            .Select( FromDataRecord )

UPDATE Mar 9 13:関連項目 この回答の定型を分割する優れたさらに微妙なコーディング手法

8
Ruben Bartelink

ペットプロジェクトでこれを取り上げました。

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

または、次の例のようなオブジェクトマッピングを使用します。


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

http://caprisoft.codeplex.com をご覧ください

4
Zack Evans

この質問は古いもので、すでに回答済みですが、...

SqlDataReaderは既にIEnumerableを実装しているので、なぜレコードのループを作成する必要があるのですか?

私は以下のメソッドを問題なく、またはパフォーマンスの問題なしに使用しました:これまで、IList、List(Of T)、IEnumerable、IEnumerable(Of T)、IQueryable、および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

強い型付けクラス

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

使用法

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
1
Kevin