我正在使用第三方库来返回数据阅读器。我想要一个简单的方法,并尽可能通用,将其转换为对象列表
例如,假设我有一个具有2个属性EmployeeId和Name的类'Employee',我希望将数据读取器(包含雇员列表)转换为List<雇员取代。
我想我别无选择,只能迭代数据阅读器的行,并为每个行转换成一个我将添加到List的Employee对象。更好的解决方案?我正在使用C#3.5,理想情况下我希望它尽可能通用,以便它适用于任何类(DataReader中的字段名称与各种对象的属性名称匹配)。
答案 0 :(得分:61)
你真的需要一个列表,还是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<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);
答案 1 :(得分: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)
函数,并通过读取匹配列使用反射设置对象的属性。但是,我个人更喜欢定义一个工厂方法(就像Joel的答案中提到的那个)并将它的委托传递给这个函数:
var list = dataReader.GetEnumerator(Employee.Create).ToList();
答案 2 :(得分: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();
答案 3 :(得分:1)
我们已经实施了以下解决方案,并感觉它运作良好。它非常简单,需要比映射器更多的连线。但是,有时手动控制很好,老实说,你连接一次就完成了。
简而言之:我们的域模型实现了一个接口,该接口有一个接收IDataReader
的方法并从中填充模型属性。然后,我们使用Generics和Reflection创建模型的实例,并在其上调用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 */)
答案 4 :(得分:1)
注意:这是.NET核心代码
一个愚蠢的高性能选项,如果你不介意外部依赖(令人惊讶的Fast Member
nuget包):
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>());
}
}
答案 5 :(得分:0)
最简单的解决方案:
var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();
然后选择它们以将它们映射到任何类型。
答案 6 :(得分: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;
}
}
}
}
答案 7 :(得分:0)
喜欢魔术
我个人讨厌在构造函数中进行手动映射,我也不喜欢自己进行反射。因此,这是另一个出色的(相当普遍的)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);
答案 8 :(得分: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;
}
}
答案 9 :(得分: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;
}