Как я могу легко конвертировать DataReader в список? [дубликат]
этот вопрос уже есть ответ здесь:
- Как улучшить уровень доступа к данным выберите метод Pattern 7 ответов
- Преобразование строк из средства чтения данных в типизированные результаты 7 ответов
у меня есть данные в DataReader
который я хочу преобразовать в List<T>
.
Каково возможное простое решение для этого?
например, в классе CustomerEntity у меня есть свойства CustomerId и CustomerName.Если мой DataReader возвращает эти два столбца в качестве данных, то как я могу преобразовать его в List<CustomerEntity>
.
9 ответов:
Я видел системы, которые используют отражение и атрибуты в свойствах или полях для отображения DataReaders на объекты. (Немного похоже на то, что делает 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, поэтому более легкая система веса может быть полезна, чтобы иметь "в рукаве"
Если вам нужно слишком много циклов IDataReader, вы увидите преимущество уменьшения кодирования (и ошибок) без изменения архитектуры системы, над которой вы работаете. Это не значит, что это хорошая архитектура для начала..
Я предполагаю, что CustomerDTO не выйдет из уровня доступа к данным и составные объекты и т. д. будут создаваться уровнем доступа к данным с использованием объектов DTO.
Я бы предложил написать метод расширения для этого:
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
- Это тип возвращаемого значения (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
самое простое решение :
var dt=new DataTable(); dt.Load(myDataReader); list<DataRow> dr=dt.AsEnumerable().ToList();
Я бы (и начал) использовать щеголеватый. Чтобы использовать ваш пример будет похоже (записано из памяти):
public List<CustomerEntity> GetCustomerList() { using (DbConnection connection = CreateConnection()) { return connection.Query<CustomerEntity>("procToReturnCustomers", commandType: CommandType.StoredProcedure).ToList(); } }
CreateConnection()
будет обрабатывать доступ к вашей БД и возврат соединения.Dapper обрабатывает отображение полей данных в свойства автоматически. Он также поддерживает несколько типов и результирующих наборов и очень быстро.
возвращает запрос
IEnumerable
отсюдаToList()
.
вы не можете просто (напрямую) преобразовать datareader в список.
вы должны перебрать все элементы в datareader и вставить в list
ниже пример кода
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); } }
очевидно
@Ian Ringrose
центральный тезис о том, что вы должны использовать библиотеку для этого, является лучшим единственным ответом здесь (следовательно, a +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 )
обновление 9 марта 13: см. Также некоторые отличные дополнительные тонкие методы кодирования, чтобы разделить шаблон в этом ответе
Я рассмотрел это в домашнем проекте.. используйте то, что вы хотите.
обратите внимание, что 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