33

我正在使用返回数据阅读器的第三方库。我想要一种简单且尽可能通用的方法将其转换为对象列表。
例如,假设我有一个具有 2 个属性 EmployeeId 和 Name 的类“Employee”,我希望将数据读取器(包含员工列表)转换为 List<Employee>。
我想我别无选择,只能遍历数据读取器的行并将它们中的每一个转换为我将添加到列表中的 Employee 对象。有更好的解决方案吗?我正在使用 C# 3.5,理想情况下我希望它尽可能通用,以便它适用于任何类(DataReader 中的字段名称与各种对象的属性名称匹配)。

4

11 回答 11

70

你真的需要一个列表,还是 IEnumerable 足够好?

我知道您希望它是通用的,但更常见的模式是在接受数据行(或 IDataRecord)的目标对象类型上有一个静态工厂方法。看起来像这样:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }

    public static Employee Create(IDataRecord record)
    {
        return new Employee
        {
           Id = record["id"],
           Name = record["name"]
        };
    }
}

.

public IEnumerable<Employee> GetEmployees()
{
    using (var reader = YourLibraryFunction())
    {
       while (reader.Read())
       {
           yield return Employee.Create(reader);
       }
    }
}

然后,如果您真的需要一个列表而不是 IEnumerable,您可以调用.ToList()结果。我想您也可以使用泛型 + 委托来使该模式的代码也更可重用。

更新: 我今天又看到了,感觉想写通用代码:

public IEnumerable<T> GetData<T>(IDataReader reader, Func<IDataRecord, T> BuildObject)
{
    try
    {
        while (reader.Read())
        {
            yield return BuildObject(reader);
        }
    }
    finally
    {
         reader.Dispose();
    }
}

//call it like this:
var result = GetData(YourLibraryFunction(), Employee.Create);
于 2009-07-29T20:53:43.187 回答
24

您可以构建一个扩展方法,例如:

public static List<T> ReadList<T>(this IDataReader reader, 
                                  Func<IDataRecord, T> generator) {
     var list = new List<T>();
     while (reader.Read())
         list.Add(generator(reader));
     return list;
}

并像这样使用它:

var employeeList = reader.ReadList(x => new Employee {
                                               Name = x.GetString(0),
                                               Age = x.GetInt32(1)
                                        });

乔尔的建议很好。您可以选择退货IEnumerable<T>。转换上面的代码很容易:

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

如果要自动将列映射到属性,代码思路是一样的。您可以generator将上述代码中的函数替换为一个函数,该函数typeof(T)通过读取匹配的列来使用反射来询问和设置对象的属性。但是,我个人更喜欢定义一个工厂方法(就像乔尔的回答中提到的那样)并将它的委托传递给这个函数:

 var list = dataReader.GetEnumerator(Employee.Create).ToList();
于 2009-07-29T20:51:35.017 回答
3

虽然我不建议将此用于生产代码,但您可以使用反射和泛型自动执行此操作:

public static class DataRecordHelper
{
    public static void CreateRecord<T>(IDataRecord record, T myClass)
    {
        PropertyInfo[] propertyInfos = typeof(T).GetProperties();

        for (int i = 0; i < record.FieldCount; i++)
        {
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (propertyInfo.Name == record.GetName(i))
                {
                    propertyInfo.SetValue(myClass, Convert.ChangeType(record.GetValue(i), record.GetFieldType(i)), null);
                    break;
                }
            }
        }
    }
}

public class Employee
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public DateTime? BirthDate { get; set; }

    public static IDataReader GetEmployeesReader()
    {
        SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString);

        conn.Open();
        using (SqlCommand cmd = new SqlCommand("SELECT EmployeeID As Id, LastName, BirthDate FROM Employees"))
        {
            cmd.Connection = conn;
            return cmd.ExecuteReader(CommandBehavior.CloseConnection);
        }
    }

    public static IEnumerable GetEmployees()
    {
        IDataReader rdr = GetEmployeesReader();
        while (rdr.Read())
        {
            Employee emp = new Employee();
            DataRecordHelper.CreateRecord<Employee>(rdr, emp);

            yield return emp;
        }
    }
}

然后,您可以使用CreateRecord<T>()数据读取器中的字段实例化任何类。

<asp:GridView ID="GvEmps" runat="server" AutoGenerateColumns="true"></asp:GridView>

GvEmps.DataSource = Employee.GetEmployees();
GvEmps.DataBind();
于 2009-07-29T22:02:08.933 回答
2

我们已经实现了以下解决方案,并且感觉它运行良好。它非常简单,并且需要比映射器做的更多的接线。然而,有时手动控制是很好的,老实说,你连接一次就完成了。

简而言之:我们的域模型实现了一个接口,该接口具有一个方法,该方法接收IDataReader并从中填充模型属性。然后我们使用泛型和反射来创建模型的一个实例并调用Parse它的方法。

我们考虑使用构造函数并将其传递IDataReader给它,但我们所做的基本性能检查似乎表明该接口始终更快(如果只是一点点)。此外,接口路由通过编译错误提供即时反馈。

我喜欢的一件事是,您可以利用下面示例中private set的属性Age并直接从数据库中设置它们。

public interface IDataReaderParser
{
    void Parse(IDataReader reader);
}

public class Foo : IDataReaderParser
{
    public string Name { get; set; }
    public int Age { get; private set; }

    public void Parse(IDataReader reader)
    {
        Name = reader["Name"] as string;
        Age = Convert.ToInt32(reader["Age"]);
    }
}

public class DataLoader
{
    public static IEnumerable<TEntity> GetRecords<TEntity>(string connectionStringName, string storedProcedureName, IEnumerable<SqlParameter> parameters = null)
                where TEntity : IDataReaderParser, new()
    {
        using (var sqlCommand = new SqlCommand(storedProcedureName, Connections.GetSqlConnection(connectionStringName)))
        {
            using (sqlCommand.Connection)
            {
                sqlCommand.CommandType = CommandType.StoredProcedure;
                AssignParameters(parameters, sqlCommand);
                sqlCommand.Connection.Open();

                using (var sqlDataReader = sqlCommand.ExecuteReader())
                {
                    while (sqlDataReader.Read())
                    {
                        //Create an instance and parse the reader to set the properties
                        var entity = new TEntity();
                        entity.Parse(sqlDataReader);
                        yield return entity;
                    }
                }
            }
        }
    }
}

要调用它,您只需提供类型参数

IEnumerable<Foo> foos = DataLoader.GetRecords<Foo>(/* params */)
于 2016-09-13T14:47:48.197 回答
2

注意:这是 .NET Core 代码

如果您不介意外部依赖项(令人惊叹的Fast Membernuget 包),这是一个愚蠢的高性能选项:

public static T ConvertToObject<T>(this SqlDataReader rd) where T : class, new()
{

    Type type = typeof(T);
    var accessor = TypeAccessor.Create(type);
    var members = accessor.GetMembers();
    var t = new T();

    for (int i = 0; i < rd.FieldCount; i++)
    {
        if (!rd.IsDBNull(i))
        {
            string fieldName = rd.GetName(i);

            if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase)))
            {
                accessor[t, fieldName] = rd.GetValue(i);
            }
        }
    }

    return t;
}

要使用:

public IEnumerable<T> GetResults<T>(SqlDataReader dr) where T : class, new()
{
    while (dr.Read())
    {
        yield return dr.ConvertToObject<T>());
    }
}
于 2017-06-19T13:01:46.290 回答
1

像魔术一样

我个人讨厌在构造函数中进行手动映射,我也不喜欢自己进行反思。所以这是另一个由美妙的(而且相当普遍的)Newtonsoft JSON 库提供的解决方案。

只有当您的属性名称与 datareader 列名称完全匹配时,它才会起作用,但它对我们来说效果很好。

...假设您有一个数据读取器名称“yourDataReader”...

        var dt = new DataTable();
        dt.Load(yourDataReader);
        // creates a json array of objects
        string json = Newtonsoft.Json.JsonConvert.SerializeObject(dt);
        // this is what you're looking for right??
        List<YourEntityType> list = 
Newtonsoft.Json.JsonConvert
.DeserializeObject<List<YourEntityType>>(json);
于 2019-10-09T16:28:14.987 回答
0

最简单的解决方案:

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

然后选择它们以将它们映射到任何类型。

于 2017-09-10T06:22:19.267 回答
0

对于 .NET Core 2.0:

这是一个与 .NET CORE 2.0 一起使用以执行 RAW SQL 并将结果映射到任意类型的 LIST 的扩展方法:

用法:

 var theViewModel = new List();
 string theQuery = @"SELECT * FROM dbo.Something";
 theViewModel = DataSQLHelper.ExecSQL(theQuery,_context);

 using Microsoft.EntityFrameworkCore;
 using System.Data;
 using System.Data.SqlClient;
 using System.Reflection;

public static List ExecSQL(string query, myDBcontext context)
 {
 using (context)
 {
 using (var command = context.Database.GetDbConnection().CreateCommand())
 {
 command.CommandText = query;
 command.CommandType = CommandType.Text;
 context.Database.OpenConnection();
                using (var result = command.ExecuteReader())
                {
                    List<T> list = new List<T>();
                    T obj = default(T);
                    while (result.Read())
                    {
                        obj = Activator.CreateInstance<T>();
                        foreach (PropertyInfo prop in obj.GetType().GetProperties())
                        {
                            if (!object.Equals(result[prop.Name], DBNull.Value))
                            {
                                prop.SetValue(obj, result[prop.Name], null);
                            }
                        }
                        list.Add(obj);
                    }
                    return list;

                }
            }
        }
    }
于 2017-09-21T10:20:14.413 回答
0

我找到了这个解决方案。

var cmd = ctx.Connection.CreateCommand();

T result = DbDataReaderdHelper.Fill<T>(cmd)

public static class DbDataReaderdHelper
{
    public static List<T> Fill<T>(DbCommand dbCommand) where T : new()
    {
        List<T> result = new List<T>();
        var reader = dbCommand.ExecuteReader();

        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Type type = typeof(T);
                T obj = (T)Activator.CreateInstance(type);
                PropertyInfo[] properties = type.GetProperties();

                foreach (PropertyInfo property in properties)
                {
                    var value = reader[property.Name];

                    try
                    {
                        if (value != null)
                        {
                            var convertedValue = TypeDescriptor.GetConverter(property.PropertyType).ConvertFromInvariantString(value.ToString());

                            property.SetValue(obj, convertedValue);
                        }
                    }
                    catch {}
                }
                result.Add(obj);
            }
        }

        reader.Close();

        return result;
    }
}
于 2020-07-23T00:08:35.660 回答
0

我的版本

用法:

var Q = await Reader.GetTable<DbRoom>("SELECT id, name FROM rooms");

PgRoom 是

public class DbRoom
{
    [Column("id")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }
}

Reader.GetTable 包含:

            using (var R = await Q.ExecuteReaderAsync())
            {
                List<T> Result = new List<T>();

                Dictionary<int, PropertyInfo> Props = new Dictionary<int, PropertyInfo>();

                foreach (var p in typeof(T).GetProperties())
                {
                    for (int i = 0; i < R.FieldCount; i++)
                    {
                        if (p.GetCustomAttributes<ColumnAttribute>().FirstOrDefault(t => t.Name == R.GetName(i)) != null
                            && p.PropertyType == R.GetFieldType(i))
                        {
                            Props.Add(i, p);
                        }
                    }
                }

                while (await R.ReadAsync())
                {
                    T row = new T();

                    foreach (var kvp in Props)
                    {
                        kvp.Value.SetValue(row, R[kvp.Key]);
                    }

                    Result.Add(row);
                }

                return Result;
            }
于 2020-08-28T01:11:37.787 回答
-1

请检查这个答案

使用反射 + lambda 表达式进行简单、快速IDataReader的转换IEnumerable<T>

https://stackoverflow.com/a/70321210/3343007

于 2021-12-11T21:36:26.190 回答