如何轻松将DataReader转换为List <T>?

Lax*_*man 112 c# datareader generic-list

我有一个数据DataReader,我希望将其转换为List<T>.对此有什么简单的解决方案?

例如,在CustomerEntity类中,我有CustomerId和CustomerName属性.如果我的DataReader将这两列作为数据返回,那么我该如何将其转换为List<CustomerEntity>.

Jon*_*eet 197

我建议为此编写一个扩展方法:

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

然后,您可以使用LINQ的ToList()方法将其转换为a,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();
}
Run Code Online (Sandbox Code Playgroud)

我实际上建议FromDataReaderCustomer(或其他地方)放一个方法:

public static Customer FromDataReader(IDataReader reader) { ... }
Run Code Online (Sandbox Code Playgroud)

这会留下:

using (IDataReader reader = ...)
{
    List<Customer> customers = reader.Select<Customer>(Customer.FromDataReader)
                                     .ToList();
}
Run Code Online (Sandbox Code Playgroud)

(我不认为类型推断在这种情况下会起作用,但我可能是错的...)

  • 请注意,这与`reader.Cast <IDataReader>()相同.选择`. (9认同)
  • 扩展方法不应该是:while(reader.Read())而不是while(drOutput.Read()) (4认同)

小智 62

我使用这种情况编写了以下方法.

首先,添加命名空间: System.Reflection

例如:T是返回类型(ClassName),dr是映射的参数DataReader

C#,调用映射方法如下:

List<Person> personList = new List<Person>();
personList = DataReaderMapToList<Person>(dataReaderForPerson);
Run Code Online (Sandbox Code Playgroud)

这是映射方法:

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;
}
Run Code Online (Sandbox Code Playgroud)

VB.NET,调用映射方法如下:

Dim personList As New List(Of Person)
personList = DataReaderMapToList(Of Person)(dataReaderForPerson)
Run Code Online (Sandbox Code Playgroud)

这是映射方法:

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
Run Code Online (Sandbox Code Playgroud)

  • 这很好用.我对C#代码有一个小建议:将以`prop.SetValue`开头的行更改为`prop.SetValue(obj,Convert.ChangeType(dr [prop.Name],prop.PropertyType),null);`.这将使代码适用于字符串以外的类型. (2认同)

Ian*_*ose 50

我见过在属性或字段上使用Reflection和属性将DataReaders映射到对象的系统.(像什么LinqToSql确实有点.)他们节省一点打字,并可能降低错误的数量编码的DBNull时等,一旦您缓存生成的代码,他们可以更快那么大多数手写代码为好,所以不要考虑了如果你这么做的话,那就是"高速公路".

有关此示例,请参阅".NET中的反射防御".

然后你可以写代码

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

    [Field("name")]
    public string CustomerName;
}
Run Code Online (Sandbox Code Playgroud)

...

using (DataReader reader = ...)
{    
   List<CustomerDTO> customers = reader.AutoMap<CustomerDTO>()
                                    .ToList();
}
Run Code Online (Sandbox Code Playgroud)

(AutoMap(),是一种扩展方法)


@Stilgar,感谢一个伟大的评论

如果,你很可能会使用更好 的NHibernate,EF或LINQ到SQL等 但是在旧的项目(或其他(有时是有效的)的原因,如"非我发明"等"存储特效的爱")并不总是可以使用ORM,所以较轻的系统可以用来"穿上你的袖子"

如果您每个人都需要编写大量IDataReader循环,您将看到减少编码(和错误)的好处,而无需更改您正在使用的系统的体系结构.这并不是说它是一个很好的架构开始......

我假设CustomerDTO不会离开数据访问层,复合对象等将由数据访问层使用DTO对象构建.

  • 这种方法的问题在于,一旦开始使用复合对象,就会遇到很多麻烦.如果客户与公司关联,则您需要公司财产.您可以进行递归,但公司可能具有List <Customer>属性,然后您必须遍历图形.为此,您需要一个表示关联的属性.这就是LINQ to SQL和Entity Framework所做的,但它们是大型产品,您无法轻松开发内部解决方案.如果你打算这样做,为什么不使用EF呢? (5认同)
  • 我需要添加什么参考来使`[Field("id")]`工作? (5认同)
  • @Stilgar使用EF对现有数据库使用"非EF规则"是一个非常令人信服的理由*不*使用EF ..(不,这个评论不会迟到6年,因为EF仍然有与之前相同的问题然后) (5认同)
  • 为什么我没有AutoMap() (3认同)
  • 你在某处实现了AutoMap吗? (3认同)

Moh*_*sen 26

最简单的解决方案:

var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很酷的解决方案+1,但请记住 DataTable 解决方案剥夺了数据读取器的最大优势,即负载需求。DataTable 首先将整个数据读取到内存中。顺便说一句,AsEnumerable 扩展方法位于 System.Data.DataSetExtensions 程序集中(我必须说,程序集的名称很奇怪,听起来更像是命名空间)。 (2认同)
  • 我在这里错过了什么吗?此解决方案不会产生“List(Of {MyType})”,它会产生“List(Of DataRow)”,这与 Op 请求的解决方案大不相同。 (2认同)

Phi*_*per 10

我会(并且已经)开始使用Dapper.使用你的例子就像(从内存中写):

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

CreateConnection() 将处理访问您的数据库并返回连接.

Dapper自动处理将数据字段映射到属性.它还支持多种类型和结果集,速度非常快.

IEnumerable因此查询返回ToList().


Ram*_*Vel 9

您不能(直接)将datareader转换为列表.

您必须遍历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);
        }

}
Run Code Online (Sandbox Code Playgroud)


Rub*_*ink 8

显而易见@Ian Ringrose的是,你应该使用库的中心论点是这里最好的单一答案(因此是+1),但是对于最小的一次性或演示代码,这里是@SLaks@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();
    }
}
Run Code Online (Sandbox Code Playgroud)

正如在@Jon Skeet答案中所说的那样

            .Select( x => new XXX( x.GetString( 0 ), x.GetString( 1 ) ) )
Run Code Online (Sandbox Code Playgroud)

bit可以被提取到一个帮助器中(我喜欢将它们转储到查询类中):

    public static XXX FromDataRecord( this IDataRecord record)
    {
        return new XXX( record.GetString( 0 ), record.GetString( 1 ) );
    }
Run Code Online (Sandbox Code Playgroud)

用作:

            .Select( FromDataRecord )
Run Code Online (Sandbox Code Playgroud)

更新3月9日13:另请参阅一些优秀的进一步细微编码技术,以便在此答案中拆分样板


Zac*_*ans 5

我已经在一个宠物项目中介绍了这个......使用你想要的。

请注意,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");
Run Code Online (Sandbox Code Playgroud)

或者使用对象映射,如下例所示。


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");
Run Code Online (Sandbox Code Playgroud)

看看http://caprisoft.codeplex.com