2012-12-22 40 views
17

(基於電子郵件的談話,現在記錄的信息共享),我已經在不同的層中使用兩種型號:如何在不同(但兼容)模型之間轉換lambda表達式?

public class TestDTO { 
    public int CustomerID { get; set; } 
} 
//... 
public class Test { 
    public int CustomerID { get; set; } 
} 

,並在我的DTO層而言拉姆達:

Expression<Func<TestDTO, bool>> fc1 = 
    (TestDTO c1) => c1.CustomerID <= 100 && c1.CustomerID >= 10; 

哪有我認爲拉姆達(一般情況下)轉換爲談論其他型號:

Expression<Func<Test, bool>> fc2 = {insert magic here, based on fc1} 

(很明顯,我們是相同的測試條件後,但我們Test型)

回答

19

要做到這一點,您必須完全重建表達式樹;參數將需要重新映射,並且現在正在與不同類型交談的所有成員訪問都需要重新應用。幸運的是,ExpressionVisitor課程讓很多這方面變得更容易;例如(做這一切在一般情況下,不只是Func<T,bool>謂語使用):

class TypeConversionVisitor : ExpressionVisitor 
{ 
    private readonly Dictionary<Expression, Expression> parameterMap; 

    public TypeConversionVisitor(
     Dictionary<Expression, Expression> parameterMap) 
    { 
     this.parameterMap = parameterMap; 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     // re-map the parameter 
     Expression found; 
     if(!parameterMap.TryGetValue(node, out found)) 
      found = base.VisitParameter(node); 
     return found; 
    } 
    protected override Expression VisitMember(MemberExpression node) 
    { 
     // re-perform any member-binding 
     var expr = Visit(node.Expression); 
     if (expr.Type != node.Type) 
     { 
      MemberInfo newMember = expr.Type.GetMember(node.Member.Name) 
             .Single(); 
      return Expression.MakeMemberAccess(expr, newMember); 
     } 
     return base.VisitMember(node); 
    } 
} 

在這裏,我們傳遞的參數字典來重新映射,應用在VisitParameter。我們還在VisitMember中檢查我們是否切換了類型(如果Visit涉及​​或其他MemberExpression,則可能發生):如果有,我們將嘗試查找另一個具有相同名稱的成員。

接下來,我們需要一種通用λ-轉換重寫方法:

// allows extension to other signatures later... 
private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from) 
    where TFrom : class 
    where TTo : class 
{ 
    // figure out which types are different in the function-signature 
    var fromTypes = from.Type.GetGenericArguments(); 
    var toTypes = typeof(TTo).GetGenericArguments(); 
    if (fromTypes.Length != toTypes.Length) 
     throw new NotSupportedException(
      "Incompatible lambda function-type signatures"); 
    Dictionary<Type, Type> typeMap = new Dictionary<Type,Type>(); 
    for (int i = 0; i < fromTypes.Length; i++) 
    { 
     if (fromTypes[i] != toTypes[i]) 
      typeMap[fromTypes[i]] = toTypes[i]; 
    } 

    // re-map all parameters that involve different types 
    Dictionary<Expression, Expression> parameterMap 
     = new Dictionary<Expression, Expression>(); 
    ParameterExpression[] newParams = 
     new ParameterExpression[from.Parameters.Count]; 
    for (int i = 0; i < newParams.Length; i++) 
    { 
     Type newType; 
     if(typeMap.TryGetValue(from.Parameters[i].Type, out newType)) 
     { 
      parameterMap[from.Parameters[i]] = newParams[i] = 
       Expression.Parameter(newType, from.Parameters[i].Name); 
     } 
     else 
     { 
      newParams[i] = from.Parameters[i]; 
     } 
    } 

    // rebuild the lambda 
    var body = new TypeConversionVisitor(parameterMap).Visit(from.Body); 
    return Expression.Lambda<TTo>(body, newParams); 
} 

這取任意Expression<TFrom>TTo,將其轉換爲Expression<TTo>,通過:

  • 發現這類型不同TFrom/TTo
  • 使用該參數重新映射
  • 使用表達式的遊客,我們剛剛創建
  • 並最終構建一個新的lambda表達式爲所需的簽名

然後,把他們放在一起,並暴露出我們的擴展方法:

public static class Helpers { 
    public static Expression<Func<TTo, bool>> Convert<TFrom, TTo>(
     this Expression<Func<TFrom, bool>> from) 
    { 
     return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from); 
    } 

    // insert from above: ConvertImpl 
    // insert from above: TypeConversionVisitor 
} 

等瞧;通用拉姆達轉換例程,與具體實施:

Expression<Func<Test, bool>> fc2 = fc1.Convert<TestDTO, Test>(); 
+5

+1爲深魔力......把你的嚮導帽子! –

+0

@Marc Gravell:你不接受這個答案嗎? –

+0

@金塔:我忘了回到它 - 接受自己的回答有一個延遲 –

5

你可以使用AutoMapper(無表達式樹):

Mapper.CreateMap<Test, TestDTO>(); 

... 

Func<TestDTO, bool> fc1 = 
    (TestDTO c1) => c1.CustomerID <= 100 && c1.CustomerID >= 10; 

Func<Test, bool> fc2 = 
    (Test t) => fc1(Mapper.Map<Test, TestDTO>(t)); 
+2

這對LINQ到對象來說很有效,但它不允許與基於'Expression 'API的ORM和其他工具一起使用。值得一提的是,這是一個明確的+1;但我不認爲這有助於預期的情況。 –

+0

@MarcGravell:夠了...... –