2009-07-15 15 views
11

Mi piacerebbe unire le seguenti espressioni:Come unire due espressioni Lambda C# senza una chiamata?

// example class 
class Order 
{ 
    List<OrderLine> Lines  
} 
class OrderLine { } 

Expression<Func<Order, List<OrderLine>>> selectOrderLines = o => o.Lines; 
Expression<Func<List<OrderLine>, Boolean>> validateOrderLines = lines => lines.Count > 0; 

// now combine those to 
Expression<Func<Order, Boolean>> validateOrder; 

ho preso a lavorare utilizzando un invoke sulle selectOrderLines e fornendo il risultato ai validateOrderLines, ma perché sto utilizzando queste espressioni in Entity Framework, Devo effettivamente creare un'espressione pulita che dovrebbe rappresentare:

Expression<Func<Order, Boolean>> validateOrder = o => o.Lines.Count > 0; 

Come posso fare questo?

risposta

19

Il modo più elegante è utilizzare Expression Visitor. In particolare, questo MSDN Blog Entry descrive come utilizzarlo per combinare predicati (usando booleano E o O) senza Invoke.

CURA Avendo realizzato combinazione booleana non è quello che si voleva, ho scritto un esempio dell'uso di ExpressionVisitor che risolve per il vostro problema particolare:

public class ParameterToMemberExpressionRebinder : ExpressionVisitor 
{ 
    ParameterExpression _paramExpr; 
    MemberExpression _memberExpr; 

    ParameterToMemberExpressionRebinder(ParameterExpression paramExpr, MemberExpression memberExpr) 
    { 
     _paramExpr = paramExpr; 
     _memberExpr = memberExpr; 
    } 

    protected override Expression Visit(Expression p) 
    { 
     return base.Visit(p == _paramExpr ? _memberExpr : p); 
    } 

    public static Expression<Func<T, bool>> CombinePropertySelectorWithPredicate<T, T2>(
     Expression<Func<T, T2>> propertySelector, 
     Expression<Func<T2, bool>> propertyPredicate) 
    { 
     var memberExpression = propertySelector.Body as MemberExpression; 

     if (memberExpression == null) 
     { 
      throw new ArgumentException("propertySelector"); 
     } 

     var expr = Expression.Lambda<Func<T, bool>>(propertyPredicate.Body, propertySelector.Parameters); 
     var rebinder = new ParameterToMemberExpressionRebinder(propertyPredicate.Parameters[0], memberExpression); 
     expr = (Expression<Func<T, bool>>)rebinder.Visit(expr); 

     return expr; 
    } 

    class OrderLine 
    { 
    } 

    class Order 
    { 
     public List<OrderLine> Lines; 
    } 

    static void test() 
    { 
     Expression<Func<Order, List<OrderLine>>> selectOrderLines = o => o.Lines; 
     Expression<Func<List<OrderLine>, Boolean>> validateOrderLines = lines => lines.Count > 0; 
     var validateOrder = ParameterToMemberExpressionRebinder.CombinePropertySelectorWithPredicate(selectOrderLines, validateOrderLines); 

     // validateOrder: {o => (o.Lines.Count > 0)} 
    } 
} 
3

funziona Questa estensione:

public static class Utility 
    { 
     public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) 
     { 
      // build parameter map (from parameters of second to parameters of first) 
      var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f); 

      // replace parameters in the second lambda expression with parameters from the first 
      var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); 

      // apply composition of lambda expression bodies to parameters from the first expression 
      return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters); 
     } 

     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) 
     { 
      return first.Compose(second, Expression.And); 
     } 

     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) 
     { 
      return first.Compose(second, Expression.Or); 
     } 
    } 

Campione utilizzando:

Expression<Func<Product, bool>> filter1 = p => a.ProductId == 1; 
Expression<Func<Product, bool>> filter2 = p => a.Text.StartWith("test"); 
Expression<Func<Product, bool>> filterCombined = filter1.And(filter2); 
+0

Cosa avrei bisogno di Per aggiungere parentesi a questo? Voglio dire Se voglio creare un filtro come (ProductId == 1 o ProductId == 2) e a.text.StartsWith ("a") – Marty

+1

Dove trovo il parametroRebinder? – h8red

+2

Se per qualche ragione stai ancora cercando parameterRebinder, puoi trovarlo su [MSDN blog] (http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities -combining-predicates.aspx) –