将数据读取器中的行转换为键入的结果
我正在使用第三方库来返回数据读取器。 我想要一个简单的方法,并尽可能通用将其转换为对象列表。
例如,假设我有一个具有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; } } } }