2016-12-10 1 views
1

을 통해 DataTable을에 객체의 속성을 쓰기 나는이 간단한 모델의 목록이 있습니다 var list = new List<Test>() { /* some items here */ };식 트리

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

합니다. 그리고 나는이 조각하여 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); 
} 

이 지금은 실행시 위의 미리보기를 할 수있는 몇 가지 표현 트리를 생성하기 위해 노력하고있어 (일반적인 방법).

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

당신이 어떤 생각을 가지고 있습니까 : 나는이 오류가 (단지 return 전에, 블록의 끝에서) .Compile() 메서드를 호출 할 때,

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

을하지만, 그러나, 내 시도는 여기에 날 잡았어 내가 여기서 뭘 놓쳤는가? 미리 감사드립니다. 건배.

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'은 매개 변수로 정의되어 있지만 람다에게 제공하지 마십시오. 그것에 대해'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 참조 예외'(bccz)가 발생합니다. 너는 나의 하루 친구를 구했다. 다시 감사합니다. –

+1

내가 도와 줬어 다행 :) –

0

, 난 당신이 성능에 대해 걱정하는 것 같아요 (당신은 EF이 발현 또는 기타 queryProvider, 권리를 전송하지 않습니다 이후)? 즉, _filler = compiled;를 추가하는 것을 잊었다는 의미입니다. 반환하기 직전에 ...

델리 게이트로 컴파일 할 때 표현식 트리의 주된 문제점은 리팩토링에 전혀 익숙하지 않으며 읽기/이해하기 어렵다는 것입니다.

이제 직접 반사를 사용하는 경우 객체에 대한 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); 
     } 
    }; 
} 

더 읽기가 쉽지 않습니까?