我有一个这个简单模型的列表:
// model:
public class Test {
public int ID { get; set; }
public string Name { get; set; }
}
为var list = new List<Test>() { /* some items here */ };
。我正在通过此代码段从DataTable
生成list
:
var dataTable = new DataTable();
dataTable.Columns.Add("ID", typeof(int));
dataTable.Columns.Add("Name", typeof(string));
foreach (var item in list) {
var dr = dataTable.NewRow();
dr["ID"] = item.ID;
dr["Name"] = item.Name;
dataTable.Rows.Add(dr);
}
现在我正在尝试生成一些表达式树,以便在运行时执行上述代码段(以通用方式)。但是,我的尝试让我来到这里:
private static Action<DataTable, IEnumerable<T>> GetAction() {
if (_filler != null)
return;
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var tableParam = Expression.Parameter(typeof(DataTable), "targetTable");
var rowsParam = Expression.Parameter(typeof(IEnumerable<T>), "rows");
var loopVariable = Expression.Parameter(typeof(T), "item");
var columnsVariable = Expression.Parameter(typeof(DataColumnCollection), "columns");
var columnsAssign = Expression.Assign(columnsVariable,
Expression.Property(tableParam, typeof(DataTable).GetProperty("Columns")));
var headerExpressions = new List<Expression>();
var bodyExpressions = new List<Expression>();
var newRowParam = Expression.Parameter(typeof(DataRow), "currentRow");
var newRowAssign = Expression.Assign(newRowParam, Expression.Call(tableParam, typeof(DataTable).GetMethod("NewRow")));
foreach (var prop in props) {
var getMethod = prop.GetGetMethod(false);
if (getMethod == null)
continue;
var attr = prop.GetCustomAttribute<UdtColumnAttribute>();
var name = attr == null ? prop.Name : attr.ColumnName;
var headerNameParam = Expression.Parameter(typeof(string), "columnName");
var headerNameAssign = Expression.Assign(headerNameParam, Expression.Constant(name, typeof(string)));
var headerTypeParam = Expression.Parameter(typeof(Type), "columnType");
var headerTypeAssign = Expression.Assign(headerTypeParam, Expression.Constant(prop.PropertyType, typeof(Type)));
var columnsAddMethod = Expression.Call(columnsVariable,
typeof(DataColumnCollection).GetMethod("Add", new[] { typeof(string), typeof(Type) }),
headerNameParam, headerTypeParam);
headerExpressions.AddRange(new Expression[] {
headerNameParam,
headerNameAssign,
headerTypeParam,
headerTypeAssign,
columnsAddMethod,
});
var indexerProp = typeof(DataRow).GetProperty("Item", new[] { typeof(string) });
var indexerParam = Expression.Property(newRowParam, indexerProp, Expression.Constant(name, typeof(string)));
var propertyReaderMethod = Expression.Call(loopVariable, getMethod);
var assign = Expression.Assign(indexerParam, Expression.TypeAs(propertyReaderMethod, typeof(object)));
bodyExpressions.AddRange(new Expression[] { indexerParam, propertyReaderMethod, assign });
}
var finalExpressions = new List<Expression>() {
tableParam,
rowsParam,
loopVariable,
columnsVariable,
columnsAssign,
newRowParam,
newRowAssign,
};
finalExpressions.AddRange(headerExpressions);
var loop = ExpressionHelper.ForEach(rowsParam, loopVariable, Expression.Block(bodyExpressions));
finalExpressions.Add(loop);
var compilable = Expression.Block(finalExpressions);
var code = compilable.ToString();
Trace.WriteLine(code);
var compiled = Expression.Lambda<Action<DataTable, IEnumerable<T>>>(compilable, tableParam, rowsParam).Compile();
return compiled;
}
但是,当我调用.Compile()
方法时(在块的末尾,就在return
之前),我收到此错误:
发生了'System.InvalidOperationException'类型的异常 System.Core.dll但未在用户代码中处理
附加信息:类型的变量'item' 引用的'TestEntity' 范围'',但未定义
你知道我错过了什么吗?提前致谢。欢呼声。
更新 这是循环生成器:
public static class ExpressionHelper {
public static Expression ForEach(Expression collection, ParameterExpression loopVar, Expression loopContent) {
var elementType = loopVar.Type;
var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);
var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
var getEnumeratorCall = Expression.Call(collection, enumerableType.GetMethod("GetEnumerator"));
var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
// The MoveNext method's actually on IEnumerator, not IEnumerator<T>
var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));
var breakLabel = Expression.Label("LoopBreak");
var loop = Expression.Block(new[] { enumeratorVar },
enumeratorAssign,
Expression.Loop(
Expression.IfThenElse(
Expression.Equal(moveNextCall, Expression.Constant(true)),
Expression.Block(new[] { loopVar },
Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
loopContent
),
Expression.Break(breakLabel)
),
breakLabel)
);
return loop;
}
}
答案 0 :(得分:1)
下面更新了工作代码,DotNetFiddle中的工作示例是 - https://dotnetfiddle.net/fyMOxe
最初您的代码有以下两个问题:
正文表达式应该包含已分离的变量和实际表达式。在您的示例中,您要在其他参数中添加ExpressionParameter
并将其传递给Body
来电。但它们应该是独立传递的。所以你必须传递第一个参数和变量列表,第二个参数传递实际表达式。
您的循环代码错过了您生成的实际表达式var dr = dataTable.NewRow();
,但未添加到循环中。并且它也错过了与dataTable.Rows.Add(dr);
的最后一次通话,因为需要将已填充的行添加回行。
在我的示例中,我修复了这两个问题,现在根据DataTable
实体列表填写Test
代码。
public class Program
{
static void Main(string[] args)
{
var data = new List<Test>()
{
new Test() {ID = 1, Name = "1Text"},
new Test() {ID = 2, Name = "2Text"},
};
var action = ExpressionHelper.GetAction<Test>();
var dataTable = new DataTable();
action(dataTable, data);
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine($"ID: {row["ID"]}, Name: {row["Name"]}");
}
}
}
public class ExpressionHelper
{
public static Action<DataTable, IEnumerable<T>> GetAction<T>()
{
//if (_filler != null)
// return null;
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var tableParam = Expression.Parameter(typeof(DataTable), "targetTable");
var rowsParam = Expression.Parameter(typeof(IEnumerable<T>), "rows");
var loopVariable = Expression.Parameter(typeof(T), "item");
var columnsVariable = Expression.Parameter(typeof(DataColumnCollection), "columns");
var columnsAssign = Expression.Assign(columnsVariable,
Expression.Property(tableParam, typeof(DataTable).GetProperty("Columns")));
var headerExpressions = new List<Expression>();
var bodyExpressions = new List<Expression>();
var headerNameParam = Expression.Parameter(typeof(string), "columnName");
var headerTypeParam = Expression.Parameter(typeof(Type), "columnType");
var newRowParam = Expression.Parameter(typeof(DataRow), "currentRow");
var newRowAssign = Expression.Assign(newRowParam, Expression.Call(tableParam, typeof(DataTable).GetMethod("NewRow")));
bodyExpressions.Add(newRowAssign);
foreach (var prop in props)
{
var getMethod = prop.GetGetMethod(false);
if (getMethod == null)
continue;
var attr = prop.GetCustomAttribute<UdtColumnAttribute>();
var name = attr == null ? prop.Name : attr.ColumnName;
var headerNameAssign = Expression.Assign(headerNameParam, Expression.Constant(name, typeof(string)));
var headerTypeAssign = Expression.Assign(headerTypeParam, Expression.Constant(prop.PropertyType, typeof(Type)));
var columnsAddMethod = Expression.Call(columnsVariable,
typeof(DataColumnCollection).GetMethod("Add", new[] { typeof(string), typeof(Type) }),
headerNameParam, headerTypeParam);
headerExpressions.AddRange(new Expression[] {
headerNameAssign,
headerTypeAssign,
columnsAddMethod,
});
var indexerProp = typeof(DataRow).GetProperty("Item", new[] { typeof(string) });
var indexerParam = Expression.Property(newRowParam, indexerProp, Expression.Constant(name, typeof(string)));
var propertyReaderMethod = Expression.Call(loopVariable, getMethod);
var assign = Expression.Assign(indexerParam, Expression.TypeAs(propertyReaderMethod, typeof(object)));
bodyExpressions.AddRange(new Expression[] { assign });
}
// we should add that row back to collection
var addRow = Expression.Call(
Expression.Property(tableParam, "Rows"),
typeof(DataRowCollection).GetMethod("Add", new Type[] {typeof(DataRow)}),
newRowParam);
bodyExpressions.Add(addRow);
var finalExpressions = new List<Expression>()
{
columnsAssign,
newRowAssign,
};
var variables = new List<ParameterExpression>()
{
loopVariable,
columnsVariable,
newRowParam,
headerNameParam,
headerTypeParam
};
finalExpressions.AddRange(headerExpressions);
var loop = ExpressionHelper.ForEach(rowsParam, loopVariable, Expression.Block(bodyExpressions));
finalExpressions.Add(loop);
var compilable = Expression.Block(variables, finalExpressions);
var code = compilable.ToString();
Trace.WriteLine(code);
var compiled = Expression.Lambda<Action<DataTable, IEnumerable<T>>>(compilable, tableParam, rowsParam).Compile();
return compiled;
}
public static Expression ForEach(Expression collection, ParameterExpression loopVar, Expression loopContent)
{
var elementType = loopVar.Type;
var enumerableType = typeof(IEnumerable<>).MakeGenericType(elementType);
var enumeratorType = typeof(IEnumerator<>).MakeGenericType(elementType);
var enumeratorVar = Expression.Variable(enumeratorType, "enumerator");
var getEnumeratorCall = Expression.Call(collection, enumerableType.GetMethod("GetEnumerator"));
var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
// The MoveNext method's actually on IEnumerator, not IEnumerator<T>
var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));
var breakLabel = Expression.Label("LoopBreak");
var loop = Expression.Block(new[] { enumeratorVar },
enumeratorAssign,
Expression.Loop(
Expression.IfThenElse(
Expression.Equal(moveNextCall, Expression.Constant(true)),
Expression.Block(new[] { loopVar },
Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
loopContent
),
Expression.Break(breakLabel)
),
breakLabel)
);
return loop;
}
}
public class Test
{
public int ID { get; set; }
public string Name { get; set; }
}
public class UdtColumnAttribute : Attribute
{
public string ColumnName { get; set; }
}
答案 1 :(得分:0)
我不确定在这里使用表达式树的原因是什么,我猜你在使用反射时担心性能(因为你不会将这个表达式发送到EF,或者任何其他的queryProvider,对吧)?这也意味着你忘了添加_filler =编译;就在回来之前......
编译为委托时表达式树的主要问题是,它们根本不是重构友好的,并且难以阅读/理解。
现在,如果您使用直接反射,那么唯一的惩罚就是getter调用对象,所有其他工作都必须要做。因此,您可以缓存该部分,然后在没有所有复杂性的情况下完成其余部分,使代码更清晰,更具可读性。
public class PropHelper
{
public PropertyInfo PropInfo {get;set;}
public Func<object, object> Getter {get;set;}
}
private static readonly ConcurrentDictionary<Type, IEnumerable<PropHelper>> s_cachedPropHelpers = new ConcurrentDictionary<Type, IEnumerable<PropHelper>>();
public static IEnumerable<PropHelper> GetPropHelpers(Type type)
{
return s_cachedPropHelpers.GetOrAdd(type, t =>
{
var props = t.GetProperties();
var result = new List<PropHelper>();
var parameter = Expression.Parameter(typeof(object));
foreach(var prop in props)
{
result.Add(new PropHelper
{
PropInfo = prop,
Getter = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.MakeMemberAccess(
Expression.Convert(parameter, t),
prop),
typeof(object)),
parameter).Compile(),
});
}
return result;
});
}
private static Action<DataTable, IEnumerable<T>> GetAction<T>()
{
return (dataTable, list) => {
var props = GetPropHelpers(typeof(T));
foreach(var prop in props)
dataTable.Columns.Add(prop.PropInfo.Name, prop.PropInfo.PropertyType);
foreach (var item in list)
{
var dr = dataTable.NewRow();
foreach(var prop in props)
dr[prop.PropInfo.Name] = prop.Getter(item);
dataTable.Rows.Add(dr);
}
};
}
这不容易阅读吗?