DataReader
に変換したいデータが にありList<T>
ます。これに対する簡単な解決策は何ですか?
たとえば、CustomerEntity クラスでは、CustomerId と CustomerName のプロパティがあります。DataReader がこれら 2 つの列をデータとして返す場合、どうすればそれを に変換できますかList<CustomerEntity>
。
DataReader
に変換したいデータが にありList<T>
ます。これに対する簡単な解決策は何ですか?
たとえば、CustomerEntity クラスでは、CustomerId と CustomerName のプロパティがあります。DataReader がこれら 2 つの列をデータとして返す場合、どうすればそれを に変換できますかList<CustomerEntity>
。
このための拡張メソッドを作成することをお勧めします。
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();
}
(この場合、型推論は機能しないと思いますが、間違っている可能性があります...)
このケースを使用して、次のメソッドを作成しました。
まず、名前空間を追加します。System.Reflection
例:T
戻り型 (クラス名) であり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
プロパティまたはフィールドのリフレクションと属性を使用してDataReaderをオブジェクトにマップするシステムを見てきました。(LinqToSqlの機能に少し似ています。)入力が少し節約され、DBNullなどのコーディング時のエラーの数が減る可能性があります。生成されたコードをキャッシュすると、ほとんどの手書きコードよりも高速になる可能性があるため、あなたがこれをたくさんしているなら「ハイロード」。
この一例については、「。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、素晴らしいコメントをありがとう
可能であれば、NHibernate、EF、Linq to Sqlなどを使用したほうがよいでしょう。 ただし、古いプロジェクトでは(または、「ここで発明されていない」、「ストアドプロシージャが大好き」など、他の(場合によっては有効な)理由で) ORMを常に使用できるとは限らないため、軽量のシステムは「袖を上げる」のに便利です。</ p>
たくさんのIDataReaderループを作成する必要がある場合は、作業中のシステムのアーキテクチャを変更することなく、コーディング(およびエラー)を減らすことができるという利点があります。それは、最初から良いアーキテクチャだと言っているわけではありません。
CustomerDTOはデータアクセス層から抜け出せず、複合オブジェクトなどはDTOオブジェクトを使用してデータアクセス層によって構築されると想定しています。
私がこの回答を書いた数年後、 Dapperが.NETの世界に入ったので、それはあなたのonw AutoMapperを書くための非常に良い出発点になるでしょう、多分それはあなたがそうする必要を完全に取り除くでしょう。
最も簡単な解決策:
var dt = new DataTable();
dt.Load(myDataReader);
List<DataRow> rows = dt.AsEnumerable();
var customers = rows.Select(dr=>new Customer(...)).ToList();
私はDapperを使い始めました(そして使い始めました) 。あなたの例を使用するには、次のようになります(メモリから書き込まれます):
public List<CustomerEntity> GetCustomerList()
{
using (DbConnection connection = CreateConnection())
{
return connection.Query<CustomerEntity>("procToReturnCustomers", commandType: CommandType.StoredProcedure).ToList();
}
}
CreateConnection()
あなたのデータベースへのアクセスと接続の返送を処理します。
Dapperは、データフィールドのプロパティへのマッピングを自動的に処理します。また、複数のタイプと結果セットをサポートし、非常に高速です。
IEnumerable
したがって、クエリはを返しますToList()
。
明らかに@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:この回答で定型文を分割するためのいくつかの優れたさらに微妙なコーディング手法も参照してください
データリーダーをリストに単純に(直接)変換することはできません。
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);
}
}
私はペットプロジェクトでこれをカバーしました..あなたが望むものを使用してください。
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 = "z.evans@caprisoft.co.za"
})
.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 = "z.evans@caprisoft.co.za"
})
.ToListEx()
.Where(contactPerson => contactPerson.FirstName == "Zack");
http://caprisoft.codeplex.comをご覧ください。
私はこの質問が古く、すでに答えられていることを知っていますが...
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