2014-04-29 105 views
0

我有以下類,對此用法不重要。最重要的是方法SetCacheItemSelector這需要一個參數,即項目佔實體AccountCacheDTO一個選擇表達式:動態選擇投影表達式

public class AccountRepositoryCache : RepositoryCache<Account, AccountCacheDTO> 
{ 
    public AccountRepositoryCache() 
    { 
     SetCacheItemSelector(x => new AccountCacheDTO 
     { 
      Id = x.Id, 
      Login = x.Login 
     }); 
    } 
} 

所以此方法的簽名是:

public void SetCacheItemSelector(Expression<Func<TEntity, TCacheItem>> selector) 

在這種情況下,TEntity是Account類,並且TCacheItem是AccountCacheDTO類。

是否有一種方法可以使用反射動態地爲Account類和AccountCacheDTO類匹配的所有屬性構建select表達式?

目標是有方法應該是這樣的:

public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>() 
{ 
... // implementation with reflection goes here 
} 

編輯:

這裏是最終實現(幾乎一樣接受的答案):

public static Expression<Func<TSource, TTarget>> BuildSelector<TSource, TTarget>() 
     { 
      Type targetType = typeof(TTarget); 
      Type sourceType = typeof(TSource); 
      ParameterExpression parameterExpression = Expression.Parameter(sourceType, "source"); 
      List<MemberBinding> bindings = new List<MemberBinding>(); 
      foreach (PropertyInfo sourceProperty in sourceType.GetProperties().Where(x => x.CanRead)) 
      { 
       PropertyInfo targetProperty = targetType.GetProperty(sourceProperty.Name); 
       if (targetProperty != null && targetProperty.CanWrite && targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType)) 
       { 
        MemberExpression propertyExpression = Expression.Property(parameterExpression, sourceProperty); 
        bindings.Add(Expression.Bind(targetProperty, propertyExpression)); 
       } 
      } 
      NewExpression newExpression = Expression.New(targetType); 
      Expression initializer = Expression.MemberInit(newExpression, bindings); 
      return Expression.Lambda<Func<TSource, TTarget>>(initializer, parameterExpression); 
     } 
+0

爲什麼'表達式>'而不是簡單的'Func <...>''? 'Expression <>'將產生一個抽象語法樹,但不是一個可執行的委託。 –

+1

@ OlivierJacot-Descombes由於'Expression'允許它在LINQ查詢提供程序中使用。 – Servy

+0

@Servy實際上,我打算將這一點與LINQ 2實體提供者一起使用。 –

回答

1

我沒有對它進行測試,但您應該可以這樣做:這只是爲了傳達一個總體思路,您應該可以根據自己的要求調整它。

public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>(TEntity entity) 
    { 
     List<MemberBinding> memberBindings = new List<MemberBinding>(); 
     MemberInitExpression body = null; 

     foreach (var entityPropertyInfo in typeof(TEntity).GetProperties()) 
     { 
      foreach (var cachePropertyInfo in typeof(TCacheItem).GetProperties()) 
      { 
       if (entityPropertyInfo.PropertyType == cachePropertyInfo.PropertyType && entityPropertyInfo.Name == cachePropertyInfo.Name) 
       { 
        var fieldExpressoin = Expression.Field(Expression.Constant(entity), entityPropertyInfo.Name); 
        memberBindings.Add(Expression.Bind(cachePropertyInfo, fieldExpressoin)); 
       } 
      } 
     } 

     var parameterExpression = Expression.Parameter(typeof(TEntity), "x"); 
     var newExpr = Expression.New(typeof(TCacheItem)); 
     body = Expression.MemberInit(newExpr, memberBindings); 
     return Expression.Lambda<Func<TEntity, TCacheItem>>(body, parameterExpression); 
    } 
+0

生成的表達式與OP中的要求以及方法簽名所生成的表達略有不同。 –

+0

@HamletHakobyan Ofcourse它是。我沒有對它進行測試,但答案是要傳達一個可根據OP的要求進行調整的總體思路。 –

0

您最好的選擇是讓System.Linq.Expressions命名空間變得非常舒服,它包含您需要的所有方法對你的方法調用進行元編碼並將它們編譯成代表。尤其請參閱Expression.Call和Lambda.Compile方法。請注意,使用Lambda.Compile,您也可以擁有一個真正的編譯Delegate,而不是表達式樹(Expression),將調用包裝爲所需的方法。 (注意:如果你以後真的需要這個表達式樹,你也可以放棄編譯步驟)

至於構建你的集合,那就是集合掃描,並且將會遍歷你的Assembly中的所有類。我強烈建議您至少利用您的程序集或將來程序集上的自定義屬性來標記它們以便進行此掃描,以免此過程最終成本更高。至多,您應該考慮使用自定義屬性來標記要爲此表達式構建掃描哪些屬性。

實際的代碼到這傾向於開始

AppDomain.CurrentDomain // Necessary to get all available Assemblies 
    .GetAssemblies() // Gets all the assemblies currently loaded in memory that this code can work with 
    .AsParallel()  // Highly recommended to make the attribute-checking steps run asynchronously 
         // Also gives you a handy .ForAll Method at the end 
    // TODO: .Where Assembly contains Attribute 
    .SelectMany(assembly => assembly.GetTypes()) 
    // TODO: .Where Type contains Attribute 
    .SelectMany(type => type.GetProperties) 
    // TODO: Make sure Property has the right data... 
    .Select(CompileFromProperty) 

凡CompileFromProperty是一種方法採取的PropertyInfo並返回所需的表達。

查找到ToList()和ToDictionary之後,你可能需要一旦你開始推值緩存打出來的並行化

附錄:你也有.MakeGenericType對Type類,這將允許您從其他類型變量中指定通用參數,這在構建表達式時將證明是非常有用的。當您定義泛型時,請不要忘記Contravariance

+0

這實際上並沒有回答這個問題。問題中所要求的實際方法的實施在這裏僅作爲「TODO」評論留下。這就是TODO,他特意要求幫忙。 – Servy

+0

其實,我的示例代碼只是關於大會掃描,這是如何找到所有匹配的屬性。實際實現如何從反射來構建表達式的地方是表達式命名空間出現的地方 – David

+0

@Aravol我很難理解代碼與我的問題之間的關係...... –

1

當然,@Aravol的答案是有道理的,但它在OP中有一點不同。這是更適合OP要求的解決方案。

public Expression<Func<TEntity, TCacheItem>> BuildSelector<TEntity, TCacheItem>() 
{ 
    Type type = typeof(TEntity); 
    Type typeDto = typeof(TCacheItem); 
    var ctor = Expression.New(typeDto); 
    ParameterExpression parameter = Expression.Parameter(type, "p"); 
    var propertiesDto = typeDto.GetProperties(BindingFlags.Public | BindingFlags.Instance); 
    var memberAssignments = propertiesDto.Select(p => 
    { 
     PropertyInfo propertyInfo = type.GetProperty(p.Name, BindingFlags.Public | BindingFlags.Instance); 
     MemberExpression memberExpression = Expression.Property(parameter, propertyInfo); 
     return Expression.Bind(p, memberExpression); 
    }); 
    var memberInit = Expression.MemberInit(ctor, memberAssignments); 
    return Expression.Lambda<Func<TEntity, TCacheItem>>(memberInit, parameter); 
}