将数据读取器中的行转换为键入的结果

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

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

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

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 GetEmployees() { using (var reader = YourLibraryFunction()) { while (reader.Read()) { yield return Employee.Create(reader); } } } 

然后,如果你真的需要一个列表而不是IEnumerable,你可以在结果上调用.ToList() 。 我想你也可以使用generics+委托来使这个模式的代码更易于重用。

更新:我今天再次看到这个,感觉就像编写通用代码:

 public IEnumerable GetData(IDataReader reader, Func BuildObject) { try { while (reader.Read()) { yield return BuildObject(reader); } } finally { reader.Dispose(); } } //call it like this: var result = GetData(YourLibraryFunction(), Employee.Create); 

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

 public static List ReadList(this IDataReader reader, Func generator) { var list = new List(); 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 。 转换上面的代码很容易:

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

如果要自动将列映射到属性,则代码构思是相同的。 您可以使用查询typeof(T)的函数替换上面代码中的generator函数,并通过读取匹配列使用reflection设置对象的属性。 但是,我个人更喜欢定义一个工厂方法(就像Joel的答案中提到的那样)并将它的委托传递给这个函数:

  var list = dataReader.GetEnumerator(Employee.Create).ToList(); 

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

 public static class DataRecordHelper { public static void CreateRecord(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(rdr, emp); yield return emp; } } } 

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

  GvEmps.DataSource = Employee.GetEmployees(); GvEmps.DataBind(); 

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

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

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

我喜欢的一件事是,你可以在下面的例子中使用像Age这样的属性的private set ,并直接从数据库中设置它们。

 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 GetRecords(string connectionStringName, string storedProcedureName, IEnumerable 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; } } } } } } 

要调用它,只需提供type参数即可

 IEnumerable foos = DataLoader.GetRecords(/* params */) 

注意:这是.NET核心代码

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

 public static T ConvertToObject(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 GetResults(SqlDataReader dr) where T : class, new() { while (dr.Read()) { yield return dr.ConvertToObject()); } } 

最简单的解决方案:

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

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

对于.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 list = new List(); T obj = default(T); while (result.Read()) { obj = Activator.CreateInstance(); 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; } } } }