Kev*_*vin 5 c# reflection expression-trees dbdatareader linq-expressions
我的属性绑定数据访问类有一个小问题(更像是一个烦恼)。问题是当读取器中不存在类中相应属性的列时,映射会失败。
这是映射器类:
// Map our datareader object to a strongly typed list
private static IList<T> Map<T>(DbDataReader dr) where T : new()
{
try
{
// initialize our returnable list
List<T> list = new List<T>();
// fire up the lamda mapping
var converter = new Converter<T>();
while (dr.Read())
{
// read in each row, and properly map it to our T object
var obj = converter.CreateItemFromRow(dr);
// add it to our list
list.Add(obj);
}
// reutrn it
return list;
}
catch (Exception ex)
{
return default(List<T>);
}
}
Run Code Online (Sandbox Code Playgroud)
转换器类:
/// <summary>
/// Converter class to convert returned Sql Records to strongly typed classes
/// </summary>
/// <typeparam name="T">Type of the object we'll convert too</typeparam>
internal class Converter<T> where T : new()
{
// Concurrent Dictionay objects
private static ConcurrentDictionary<Type, object> _convertActionMap = new ConcurrentDictionary<Type, object>();
// Delegate action declaration
private Action<IDataReader, T> _convertAction;
// Build our mapping based on the properties in the class/type we've passed in to the class
private static Action<IDataReader, T> GetMapFunc()
{
var exps = new List<Expression>();
var paramExp = Expression.Parameter(typeof(IDataReader), "o7thDR");
var targetExp = Expression.Parameter(typeof(T), "o7thTarget");
var getPropInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) });
var _props = typeof(T).GetProperties();
foreach (var property in _props)
{
var getPropExp = Expression.MakeIndex(paramExp, getPropInfo, new[] { Expression.Constant(property.Name, typeof(string)) });
var castExp = Expression.TypeAs(getPropExp, property.PropertyType);
var bindExp = Expression.Assign(Expression.Property(targetExp, property), castExp);
exps.Add(bindExp);
}
// return our compiled mapping, this will ensure it is cached to use through our record looping
return Expression.Lambda<Action<IDataReader, T>>(Expression.Block(exps), new[] { paramExp, targetExp }).Compile();
}
internal Converter()
{
// Fire off our mapping functionality
_convertAction = (Action<IDataReader, T>)_convertActionMap.GetOrAdd(typeof(T), (t) => GetMapFunc());
}
internal T CreateItemFromRow(IDataReader dataReader)
{
T result = new T();
_convertAction(dataReader, result);
return result;
}
}
Run Code Online (Sandbox Code Playgroud)
System.IndexOutOfRangeException {"Mileage"}
Run Code Online (Sandbox Code Playgroud)
at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at System.Data.SqlClient.SqlDataReader.get_Item(String name)
at lambda_method(Closure , IDataReader , Typing )
at o7th.Class.Library.Data.Converter`1.CreateItemFromRow(IDataReader dataReader) in d:\Backup Folder\Development\o7th Web Design\o7th.Class.Library.C-Sharp\o7th.Class.Library\Data Access Object\Converter.cs:line 50
at o7th.Class.Library.Data.Wrapper.Map[T](DbDataReader dr) in d:\Backup Folder\Development\o7th Web Design\o7th.Class.Library.C-Sharp\o7th.Class.Library\Data Access Object\Wrapper.cs:line 33
Run Code Online (Sandbox Code Playgroud)
我该如何修复它,以便当我有一个读者可能没有的额外属性作为列时它不会失败,反之亦然?当然,快速创可贴只是NULL As Mileage在示例中简单地添加到此查询中,但是,这不是问题的解决方案:)
这里Map<T>使用反射:
// Map our datareader object to a strongly typed list
private static IList<T> Map<T>(DbDataReader dr) where T : new()
{
try
{
// initialize our returnable list
List<T> list = new List<T>();
T item = new T();
PropertyInfo[] properties = (item.GetType()).GetProperties();
while (dr.Read()) {
int fc = dr.FieldCount;
for (int j = 0; j < fc; ++j) {
var pn = properties[j].Name;
var gn = dr.GetName(j);
if (gn == pn) {
properties[j].SetValue(item, dr[j], null);
}
}
list.Add(item);
}
// return it
return list;
}
catch (Exception ex)
{
// Catch an exception if any, an write it out to our logging mechanism, in addition to adding it our returnable message property
_Msg += "Wrapper.Map Exception: " + ex.Message;
ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.Wrapper.Map", _Msg);
// make sure this method returns a default List
return default(List<T>);
}
}
Run Code Online (Sandbox Code Playgroud)
注意: 此方法比使用表达式树慢 63%...
如评论中所述,问题在于读取器中不存在指定属性的列。这个想法是首先循环读取器的列名,然后检查是否存在匹配的属性。但是如何预先获取列名称列表呢?
一种想法是使用表达式树本身从读取器构建列名称列表,并根据类的属性对其进行检查。像这样的东西
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));
Run Code Online (Sandbox Code Playgroud)
相当于
List<string> columnNames = new List<string>();
for (int i = 0; i < reader.FieldCount; i++)
{
columnNames.Add(reader.GetName(i));
}
Run Code Online (Sandbox Code Playgroud)
人们可以继续最后的表达,但这里有一个问题,使得沿着这条线的任何进一步努力都是徒劳的。每次调用最终委托时,上面的表达式树都会获取列名称,在您的情况下,这是针对每个对象创建的,这违背了您的要求的精神。
另一种方法是通过属性(请参阅示例)或通过维护静态字典(如 ( 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;
}
Run Code Online (Sandbox Code Playgroud)
现在这就引出了一个问题,为什么不将数据读取器直接传递给构造函数呢?那会更好。
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;
}
Run Code Online (Sandbox Code Playgroud)
称呼它为
List<T> list = new List<T>();
var converter = new Converter<T>(dr);
while (dr.Read())
{
var obj = converter.CreateItemFromRow();
list.Add(obj);
}
Run Code Online (Sandbox Code Playgroud)不过,我可以建议一些改进。
new T()您调用的泛型速度CreateItemFromRow较慢,它在幕后使用反射。您也可以将该部分委托给表达式树,这应该更快
现在,GetProperty调用不区分大小写,这意味着您的列名称必须与属性名称完全匹配。我会使用其中之一使其不区分大小写Bindings.Flag。
我完全不确定为什么你ConcurrentDictionary在这里使用 a 作为缓存机制。泛型类中的静态字段<T>对于每个T. 通用字段本身可以充当缓存。另外为什么type 的Value部分是?ConcurrentDictionaryobject
正如我之前所说,将类型和列名称强绑定并不是最好的方法(通过为每个类型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);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4155 次 |
| 最近记录: |