2012-12-22 15 views
17

(dựa trên một cuộc trò chuyện email, bây giờ ghi cho việc chia sẻ thông tin) Tôi đã hai mô hình được sử dụng ở các lớp khác nhau:Làm thế nào tôi có thể chuyển đổi một biểu thức lambda giữa các mô hình khác nhau (nhưng tương thích)?

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

và lambda về lớp DTO của tôi:

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

thế nào có thể Tôi chuyển đổi lambda đó (trong trường hợp chung) để nói về mô hình khác:

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

(rõ ràng, chúng tôi đang theo cùng điều kiện kiểm tra, nhưng chúng tôi ing loại Test)

?

Trả lời

19

Để làm điều đó, bạn sẽ phải xây dựng lại toàn bộ cây biểu thức; các tham số sẽ cần ánh xạ lại và tất cả các thành viên truy cập hiện đang nói chuyện với các loại khác nhau sẽ cần phải được áp dụng lại. May mắn thay, rất nhiều điều này được thực hiện dễ dàng hơn bởi lớp ExpressionVisitor; ví dụ (làm tất cả trong trường hợp tổng quát, không chỉ là việc sử dụng Func<T,bool> vị ngữ):

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

Ở đây, chúng tôi vượt qua trong một cuốn từ điển các thông số để tái bản đồ, áp dụng trong VisitParameter. Chúng tôi cũng có, trong VisitMember, hãy kiểm tra xem chúng tôi đã chuyển loại (có thể xảy ra nếu Visit liên quan đến một số ParameterExpression hoặc khác MemberExpression, tại bất kỳ thời điểm nào): nếu có, chúng tôi sẽ thử và tìm một thành viên khác có cùng tên.

Tiếp theo, chúng ta cần một phương pháp mục đích chung lambda-chuyển đổi Ổ ghi:

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

này có một tùy Expression<TFrom>, và một TTo, chuyển đổi nó vào một Expression<TTo>, bởi:

  • Phát hiện mà các loại khác nhau giữa TFrom/TTo
  • sử dụng để sắp xếp lại các tham số
  • sử dụng biểu thức-khách truy cập chúng tôi vừa tạo
  • và cuối cùng là xây dựng một biểu thức lambda mới cho chữ ký mong muốn

Sau đó, đặt nó tất cả cùng nhau và phơi bày phương pháp mở rộng của chúng tôi:

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 
} 

et thì đấy; một mục đích chung thường xuyên chuyển đổi lambda, với một thực hiện cụ thể của:

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

+1 cho wizardry sâu ... Đặt trên wizard mũ! –

+0

@Marc Gravell: Bạn không chấp nhận câu trả lời này? –

+0

@Kin ta: Tôi quên quay trở lại với nó - có sự chậm trễ khi chấp nhận câu trả lời tự –

5

Bạn có thể sử dụng AutoMapper (không có cây biểu thức):

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

Điều đó sẽ làm việc cho LINQ-to-Objects, nhưng nó sẽ không cho phép sử dụng với ORM và các công cụ khác dựa trên 'Expression ' API. Đáng nói đến, mặc dù vậy, một +1 nhất định; nhưng tôi không nghĩ rằng nó sẽ giúp trong trường hợp dự định. –

+0

@MarcGravell: Đủ công bằng .... –