C# 写一个对象';通过表达式树将属性设置为DataTable

C# 写一个对象';通过表达式树将属性设置为DataTable,c#,c#-4.0,lambda,expression-trees,C#,C# 4.0,Lambda,Expression Trees,我有一个简单模型的列表: // model: public class Test { public int ID { get; set; } public string Name { get; set; } } asvar list=newlist(){/*此处的某些项*/}。我正在从列表通过以下代码片段生成一个数据表: var dataTable = new DataTable(); dataTable.Columns.Add("ID", typeof(int)); data

我有一个简单模型的列表:

// model:
public class Test {
    public int ID { get; set; }
    public string Name { get; set; }
}
as
var list=newlist(){/*此处的某些项*/}。我正在从
列表
通过以下代码片段生成一个
数据表

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;
    }

更新后的工作代码如下,DotNetFiddle中的工作示例为-

最初,您的代码存在以下两个问题:

  • 主体表达式应该有单独的变量和实际表达式。在示例中,您正在其他参数中添加
    ExpressionParameter
    ,并将它们传递给
    Body
    call。但它们应该单独通过。因此,必须用变量列表传递第一个参数,用实际表达式传递第二个参数

  • 循环代码缺少实际表达式
    var dr=dataTable.NewRow()是您生成的,但未添加到循环中的。它还错过了上次调用
    dataTable.Rows.Add(dr)已填充行需要添加回行

  • 在我的示例中,我修复了这两个问题,现在代码根据
    Test
    实体列表填充
    DataTable

    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; }
    }
    
    公共类程序
    {
    静态void Main(字符串[]参数)
    {
    变量数据=新列表()
    {
    新测试(){ID=1,Name=“1Text”},
    新测试(){ID=2,Name=“2Text”},
    };
    var action=ExpressionHelper.GetAction();
    var dataTable=新的dataTable();
    动作(数据表、数据);
    foreach(dataTable.Rows中的DataRow行)
    {
    WriteLine($“ID:{row[“ID”]},Name:{row[“Name”]}”);
    }
    }
    }
    公共类ExpressionHelper
    {
    公共静态操作GetAction()
    {
    //如果(_filler!=null)
    //返回null;
    var类型=类型(T);
    var props=type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    var tableParam=Expression.Parameter(typeof(DataTable),“targetTable”);
    var rowsParam=Expression.Parameter(typeof(IEnumerable),“rows”);
    var loopVariable=表达式参数(类型(T),“项”);
    var columnsVariable=Expression.Parameter(typeof(DataColumnCollection),“columns”);
    var columnsAssign=Expression.Assign(columnsVariable,
    Expression.Property(tableParam,typeof(DataTable.GetProperty(“Columns”));
    var headerExpressions=新列表();
    var bodyExpressions=新列表();
    var headerNameParam=Expression.Parameter(typeof(string),“columnName”);
    var headerTypeParam=表达式.参数(typeof(Type),“columnType”);
    var newRowParam=Expression.Parameter(typeof(DataRow),“currentRow”);
    var newrowsign=Expression.Assign(newRowParam,Expression.Call(tableParam,typeof(DataTable.GetMethod)(“NewRow”));
    bodyExpressions.Add(newrowsign);
    foreach(道具中的var道具)
    {
    var getMethod=prop.getMethod(false);
    if(getMethod==null)
    继续;
    var attr=prop.GetCustomAttribute();
    var name=attr==null?属性名称: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(“添加”,新[]{typeof(字符串),typeof(类型)}),
    headerNameParam、headerTypeParam);
    headerExpressions.AddRange(新表达式[]){
    头像标志,
    校长类型分配,
    柱状法,
    });
    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));
    AddRange(新表达式[]{assign});
    }
    //我们应该将该行添加回集合
    var addRow=Expression.Call(
    Expression.Property(tableParam,“行”),
    typeof(DataRowCollection).GetMethod(“Add”,新类型[]{typeof(DataRow)}),
    newRowParam);
    bodyExpressions.Add(addRow);
    var finalExpressions=新列表()
    {
    柱状符号,
    纽罗,
    };
    var变量=新列表()
    {
    循环变量,
    列变量,
    newRowParam,
    海德纳梅帕拉姆,
    headerTypeParam
    };
    最终表达式.AddRange(headerExpressions);
    var loop=ExpressionHelper.ForEach(rowsParam,loopVariable,Expression.Block(bodyExpressions));
    添加(循环);
    var compileable=Expression.Block(变量、finalExpressions);
    var code=compileable.ToString();
    Trace.WriteLine(代码);
    var compiled=Expression.Lambda(可编译,tableParam,rowsParam).Compile();
    编制报表;
    }
    公共静态表达式ForEach(表达式集合、参数Expression loopVar、表达式loopContent)
    {
    var elementType=loopVar.Type;
    var enumerableType=typeof(IEnumerable);
    var enumeratorType=typeof(IEnumerator)。MakeGenericType(elementType);
    var enumeratorVar=Expression.Variable(enumeratorType,“枚举器”);
    var getEnumeratorCall=Expressi
    
    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; }
    }
    
    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);
            }
        };
    }