Combinando dos expresiones (Expresión <Func >)

Tengo dos expresiones de tipo Expression<Func> y quiero llevar a O, Y o NO de estas y obtener una nueva expresión del mismo tipo

 Expression<Func> expr1; Expression<Func> expr2; ... //how to do this (the code below will obviously not work) Expression<Func> andExpression = expr AND expr2 

Bueno, puedes usar Expression.AndAlso / OrElse etc. para combinar expresiones lógicas, pero el problema son los parámetros; ¿Estás trabajando con la misma ParameterExpression en expr1 y expr2? Si es así, es más fácil:

 var body = Expression.AndAlso(expr1.Body, expr2.Body); var lambda = Expression.Lambda>(body, expr1.Parameters[0]); 

Esto también funciona bien para negar una sola operación:

 static Expression> Not( this Expression> expr) { return Expression.Lambda>( Expression.Not(expr.Body), expr.Parameters[0]); } 

De lo contrario, dependiendo del proveedor de LINQ, es posible que pueda combinarlos con Invoke :

 // OrElse is very similar... static Expression> AndAlso( this Expression> left, Expression> right) { var param = Expression.Parameter(typeof(T), "x"); var body = Expression.AndAlso( Expression.Invoke(left, param), Expression.Invoke(right, param) ); var lambda = Expression.Lambda>(body, param); return lambda; } 

En alguna parte, tengo un código que vuelve a escribir un árbol de expresiones reemplazando nodos para eliminar la necesidad de Invoke , pero es bastante largo (y no recuerdo dónde lo dejé …)


Versión generalizada que elige la ruta más simple:

 static Expression> AndAlso( this Expression> expr1, Expression> expr2) { // need to detect whether they use the same // parameter instance; if not, they need fixing ParameterExpression param = expr1.Parameters[0]; if (ReferenceEquals(param, expr2.Parameters[0])) { // simple version return Expression.Lambda>( Expression.AndAlso(expr1.Body, expr2.Body), param); } // otherwise, keep expr1 "as is" and invoke expr2 return Expression.Lambda>( Expression.AndAlso( expr1.Body, Expression.Invoke(expr2, param)), param); } 

A partir de .net 4.0. Existe la clase ExpressionVistor que le permite construir expresiones que sean EF seguras.

  public static Expression> AndAlso( this Expression> expr1, Expression> expr2) { var parameter = Expression.Parameter(typeof (T)); var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); var left = leftVisitor.Visit(expr1.Body); var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); var right = rightVisitor.Visit(expr2.Body); return Expression.Lambda>( Expression.AndAlso(left, right), parameter); } private class ReplaceExpressionVisitor : ExpressionVisitor { private readonly Expression _oldValue; private readonly Expression _newValue; public ReplaceExpressionVisitor(Expression oldValue, Expression newValue) { _oldValue = oldValue; _newValue = newValue; } public override Expression Visit(Expression node) { if (node == _oldValue) return _newValue; return base.Visit(node); } } 

Puede usar Expression.AndAlso / OrElse para combinar expresiones lógicas, pero debe asegurarse de que las ParameterExpressions sean las mismas.

Estaba teniendo problemas con EF y PredicateBuilder, así que hice lo mío sin recurrir a Invoke, que podría usar así:

 var filterC = filterA.And(filterb); 

Código fuente para mi PredicateBuilder:

 public static class PredicateBuilder { public static Expression> And(this Expression> a, Expression> b) { ParameterExpression p = a.Parameters[0]; SubstExpressionVisitor visitor = new SubstExpressionVisitor(); visitor.subst[b.Parameters[0]] = p; Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body)); return Expression.Lambda>(body, p); } public static Expression> Or(this Expression> a, Expression> b) { ParameterExpression p = a.Parameters[0]; SubstExpressionVisitor visitor = new SubstExpressionVisitor(); visitor.subst[b.Parameters[0]] = p; Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body)); return Expression.Lambda>(body, p); } } 

Y la clase de utilidad para sustituir los parámetros en un lambda:

 internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor { public Dictionary subst = new Dictionary(); protected override Expression VisitParameter(ParameterExpression node) { Expression newValue; if (subst.TryGetValue(node, out newValue)) { return newValue; } return node; } } 

Joe Albahari (autor de C # 3.0 en Nutshell y LINQPad) escribió una utilidad llamada PredicateBuilder que se puede usar para las funciones AND y OR juntas.

http://www.albahari.com/nutshell/predicatebuilder.aspx

Si bien funciona en funciones, es de código abierto para que pueda verificarlo y ver cómo funciona.

Si su proveedor no admite Invoke y necesita combinar dos expresiones, puede usar un ExpressionVisitor para reemplazar el parámetro en la segunda expresión por el parámetro en la primera expresión.

 class ParameterUpdateVisitor : ExpressionVisitor { private ParameterExpression _oldParameter; private ParameterExpression _newParameter; public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter) { _oldParameter = oldParameter; _newParameter = newParameter; } protected override Expression VisitParameter(ParameterExpression node) { if (object.ReferenceEquals(node, _oldParameter)) return _newParameter; return base.VisitParameter(node); } } static Expression> UpdateParameter( Expression> expr, ParameterExpression newParameter) { var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter); var body = visitor.Visit(expr.Body); return Expression.Lambda>(body, newParameter); } [TestMethod] public void ExpressionText() { string text = "test"; Expression> expr1 = p => p.Item1.Contains(text); Expression> expr2 = q => q.Item2.Contains(text); Expression> expr3 = UpdateParameter(expr2, expr1.Parameters[0]); var expr4 = Expression.Lambda>( Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]); var func = expr4.Compile(); Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" })); } 

Sugiero una mejora más para las soluciones PredicateBuilder y ExpressionVisitor . Lo llamé UnifyParametersByName y lo puedes encontrar en la biblioteca MIT mía: LinqExprHelper . Permite combinar expresiones lambda arbitrarias. Por lo general, las preguntas se hacen sobre la expresión del predicado, pero esta idea se extiende también a las expresiones de proyección.

El siguiente código emplea un método ExprAdres que crea una expresión parametrizada complicada, utilizando lambda en línea. Esta expresión complicada está codificada solo una vez, y luego reutilizada, gracias a la LinqExprHelper .

 public IQueryable UbezpFull { get { System.Linq.Expressions.Expression< Func> expr = (u, parAdrM, parAdrZ) => new UbezpExt { Ub = u, AdrM = parAdrM, AdrZ = parAdrZ, }; // From here an expression builder ExprAdres is called. var expr2 = expr .ReplacePar("parAdrM", ExprAdres("M").Body) .ReplacePar("parAdrZ", ExprAdres("Z").Body); return UBEZPIECZONY.Select((Expression>)expr2); } } 

Y este es el código de construcción de subexpresiones:

 public static Expression> ExprAdres(string sTyp) { return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp) .OrderByDescending(a => a.DATAOD).FirstOrDefault(); } 

Lo que traté de lograr fue realizar consultas parametrizadas sin necesidad de copiar y pegar y con la capacidad de usar lambdas en línea, que son muy bonitas. Sin todas estas cosas de expresión de ayuda, me vería obligado a crear una consulta completa de una vez.

Necesitaba lograr los mismos resultados, pero usando algo más genérico (ya que el tipo no se conocía). Gracias a la respuesta de Marc, finalmente descubrí lo que estaba tratando de lograr:

  public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp) { var parameter = Expression.Parameter(sourceType); var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter); var left = leftVisitor.Visit(exp.Body); var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter); var right = rightVisitor.Visit(newExp.Body); var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool)); return Expression.Lambda(delegateType, Expression.Or(left, right), parameter); } 

Creo que esto funciona bien, ¿no?

 Func expr1 = (x => x.Att1 == "a"); Func expr2 = (x => x.Att2 == "b"); Func expr1ANDexpr2 = (x => expr1(x) && expr2(x)); Func expr1ORexpr2 = (x => expr1(x) || expr2(x)); Func NOTexpr1 = (x => !expr1(x));