如评论中指出的那样,问题在于,指定属性的读取器中没有列。这个想法是首先按读取器的列名循环,并检查是否存在匹配属性。但是,如何事先获得列名列表?
一种想法是使用表达式树本身从读者中构建列名列表,并根据类的属性进行检查。这样的东西
var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR"); var loopIncrementVariableExp = Expression.Parameter(typeof(int), "i"); var columnNamesExp = Expression.Parameter(typeof(List<string>), "columnNames"); var columnCountExp = Expression.Property(paramExp, "FieldCount"); var getColumnNameExp = Expression.Call(paramExp, "GetName", Type.EmptyTypes, Expression.PostIncrementAssign(loopIncrementVariableExp)); var addToListExp = Expression.Call(columnNamesExp, "Add", Type.EmptyTypes, getColumnNameExp); var labelExp = Expression.Label(columnNamesExp.Type); var getColumnNamesExp = Expression.Block( new[] { loopIncrementVariableExp, columnNamesExp }, Expression.Assign(columnNamesExp, Expression.New(columnNamesExp.Type)), Expression.Loop( Expression.IfThenElse( Expression.LessThan(loopIncrementVariableExp, columnCountExp), addToListExp, Expression.Break(labelExp, columnNamesExp)), labelExp));
等效于
List<string> columnNames = new List<string>(); for (int i = 0; i < reader.FieldCount; i++) { columnNames.Add(reader.GetName(i)); }
一个人可能会继续进行最终表达,但是这里有一个陷入困境的事情,沿着这条线徒劳无功。每次调用最终代表时,上面的表达树都将获取列名,而在您的情况下,这是针对每个对象创建的,这是违背您要求的精神的。
另一种方法是让转换器类具有对给定类型的列名的预定义意识,通过属性(见一个例子)或通过保持静态词典(例如
Dictionary<Type, IEnumerable<string>>
)。尽管它具有更大的灵活性,但另一方面是您的查询不一定总是包含表的所有列名,任何reader[notInTheQueryButOnlyInTheTableColumn]
会导致例外。我看到的最好的方法是从读取器对象中获取列名,但只有一次。我会重写这样的东西:
private static List<string> columnNames; private static Action<IDataReader, T> GetMapFunc() { var exps = new List<Expression>(); var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR"); var targetExp = Expression.Parameter(typeof(T), "o7thTarget"); var getPropInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }); foreach (var columnName in columnNames) { var property = typeof(T).GetProperty(columnName); if (property == null) continue; // use 'columnName' instead of 'property.Name' to speed up reader lookups //in case of certain readers. var columnNameExp = Expression.Constant(columnName); var getPropExp = Expression.MakeIndex( paramExp, getPropInfo, new[] { columnNameExp }); var castExp = Expression.TypeAs(getPropExp, property.PropertyType); var bindExp = Expression.Assign( Expression.Property(targetExp, property), castExp); exps.Add(bindExp); } return Expression.Lambda<Action<IDataReader, T>>( Expression.Block(exps), paramExp, targetExp).Compile(); } internal T CreateItemFromRow(IDataReader dataReader) { if (columnNames == null) { columnNames = Enumerable.Range(0, dataReader.FieldCount) .Select(x => dataReader.GetName(x)) .ToList(); _convertAction = (Action<IDataReader, T>)_convertActionMap.GetOrAdd( typeof(T), (t) => GetMapFunc()); } T result = new T(); _convertAction(dataReader, result); return result; }
现在提出了一个问题,为什么不直接将数据读取器传递给构造函数?那会更好。
private IDataReader dataReader; private Action<IDataReader, T> GetMapFunc() { var exps = new List<Expression>(); var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR"); var targetExp = Expression.Parameter(typeof(T), "o7thTarget"); var getPropInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }); var columnNames = Enumerable.Range(0, dataReader.FieldCount) .Select(x => dataReader.GetName(x)); foreach (var columnName in columnNames) { var property = typeof(T).GetProperty(columnName); if (property == null) continue; // use 'columnName' instead of 'property.Name' to speed up reader lookups //in case of certain readers. var columnNameExp = Expression.Constant(columnName); var getPropExp = Expression.MakeIndex( paramExp, getPropInfo, new[] { columnNameExp }); var castExp = Expression.TypeAs(getPropExp, property.PropertyType); var bindExp = Expression.Assign( Expression.Property(targetExp, property), castExp); exps.Add(bindExp); } return Expression.Lambda<Action<IDataReader, T>>( Expression.Block(exps), paramExp, targetExp).Compile(); } internal Converter(IDataReader dataReader) { this.dataReader = dataReader; _convertAction = (Action<IDataReader, T>)_convertActionMap.GetOrAdd( typeof(T), (t) => GetMapFunc()); } internal T CreateItemFromRow() { T result = new T(); _convertAction(dataReader, result); return result; }
称其为
List<T> list = new List<T>(); var converter = new Converter<T>(dr); while (dr.Read()) { var obj = converter.CreateItemFromRow(); list.Add(obj); }
不过,我可以提出许多改进。
通用
new T()
您正在打电话CreateItemFromRow
较慢, 它在幕后使用反射. 。您也可以将该部分委派给表达树 应该更快马上
GetProperty
呼叫不是不敏感的,这意味着您的列名必须与属性名称完全匹配。我会用其中之一使情况变得不敏感Bindings.Flag
.我完全不确定你为什么要使用
ConcurrentDictionary
作为缓存机制。 通用类中的静态字段<T>
每个人都将是独特的T
. 。通用字段本身可以充当缓存。还为什么Value
部分ConcurrentDictionary
类型object
?正如我之前说的那样,这不是最好将类型和列的名称加强(您通过缓存一个特定的名称做到这一点)
Action
委托 类型)。即使对于相同的类型,您的查询也可能是不同的选择一组列集。最好把它留给数据阅读器来决定。利用
Expression.Convert
代替Expression.TypeAs
用于价值类型转换object
.
我会重写整个内容:
readonly Func<IDataReader, T> _converter;
readonly IDataReader dataReader;
private Func<IDataReader, T> GetMapFunc()
{
var exps = new List<Expression>();
var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR");
var targetExp = Expression.Variable(typeof(T));
exps.Add(Expression.Assign(targetExp, Expression.New(targetExp.Type)));
//does int based lookup
var indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) });
var columnNames = Enumerable.Range(0, dataReader.FieldCount)
.Select(i => new { i, name = dataReader.GetName(i) });
foreach (var column in columnNames)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnNameExp = Expression.Constant(column.i);
var propertyExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnNameExp });
var convertExp = Expression.Convert(propertyExp, property.PropertyType);
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), convertExp);
exps.Add(bindExp);
}
exps.Add(targetExp);
return Expression.Lambda<Func<IDataReader, T>>(
Expression.Block(new[] { targetExp }, exps), paramExp).Compile();
}
internal Converter(IDataReader dataReader)
{
this.dataReader = dataReader;
_converter = GetMapFunc();
}
internal T CreateItemFromRow()
{
return _converter(dataReader);
}