2016-12-10 22 views
1

寫一個對象的屬性到DataTable我有這個簡單的模型的列表:通過表達式樹

// model: 
public class Test { 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

var list = new List<Test>() { /* some items here */ };。而我通過這個片段生成從list一個DataTable

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之前)我得到這個錯誤:

An exception of type 'System.InvalidOperationException' occurred in System.Core.dll but was not handled in user code

Additional information: variable 'item' of type 'TestEntity' referenced from scope '', but it is not defined

你有什麼想法我在這裏錯過了什麼?提前致謝。乾杯。

UPDATE: 這裏是循環發電機:

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

'loopVariable'被定義爲一個參數,但是你永遠不會把它提供給你的lambda。你的意思是使用'Expression.Variable'嗎? – Rob

+0

@Rob \t 我將'loopVariable'改爲'Expression.Variable',但問題存在。 –

回答

1

更新工作代碼如下,並在DotNetFiddle工作的例子是 - https://dotnetfiddle.net/fyMOxe

本來你的代碼有以下兩個問題:

  1. 身體表達應該有分離變量和實際的表達。在您的示例中,您將在其他參數中添加ExpressionParameter,並將它們傳遞給Body調用。但他們應該單獨通過。所以你必須傳遞第一個參數和變量列表,第二個參數和實際表達式。

  2. 您的循環代碼錯過了您生成的實際表達式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; } 
} 
+0

感謝兄弟。我想出了第一個問題,並能夠編譯表達式;但調用編譯後的表達式時,拋出了第二個問題的'null reference exception' - bcuz - 我試圖修復這個問題。你救了我的一天夥計。再次感謝你。 –

+1

很高興我幫忙:) –

0

我不確定什麼是這裏使用表達式樹的原因,我猜你擔心性能,如果使用反射(因爲你不會發這句話來EF,或任何其他queryProvider,右)?這也意味着你忘了添加_filler =編譯;在返回之前......

表達式樹編譯爲委託時的主要問題是,它們根本不具有重構友好性,並且難以閱讀/理解。

現在,如果您要直接使用反射,比唯一的懲罰是對物體上的吸氣劑調用,您必須做的所有其餘工作。因此,您可以緩存該部分,然後完成所有其他部分,而不需要太複雜,代碼更清晰,可讀性更強。

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

這是不是更容易閱讀?