español c# linq lambda expression

c# - español - linq expression



Combinando dos expresiones(Expresión<Func<T, bool>>) (7)

Tengo dos expresiones de tipo Expression<Func<T, bool>> y quiero usar OR, AND o NOT de éstas y obtener una nueva expresión del mismo tipo

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


Bueno, puedes usar Expression.AndAlso / OrElse etc. para combinar expresiones lógicas, pero el problema son los parámetros; ¿está 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<Func<T,bool>>(body, expr1.Parameters[0]);

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

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

De lo contrario, dependiendo del proveedor de LINQ, puede combinarlos con Invoke :

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

En algún lugar, tengo un código que reescribe un árbol de expresiones que reemplaza los nodos para eliminar la necesidad de Invoke , pero es bastante largo (y no puedo recordar dónde lo dejé ...)

Versión generalizada que escoge la ruta más sencilla:

static Expression<Func<T, bool>> AndAlso<T>( this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> 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<Func<T, bool>>( Expression.AndAlso(expr1.Body, expr2.Body), param); } // otherwise, keep expr1 "as is" and invoke expr2 return Expression.Lambda<Func<T, bool>>( Expression.AndAlso( expr1.Body, Expression.Invoke(expr2, param)), param); }

A partir de .net 4.0. Existe la clase ExpressionVistor que le permite crear expresiones que son seguras para EF.

public static Expression<Func<T, bool>> AndAlso<T>( this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> 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<Func<T, bool>>( 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); } }


Creo que esto funciona bien, ¿no?

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


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

PredicateBuilder

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


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


Puede usar Expression.AndAlso / OrElse para combinar expresiones lógicas, pero debe asegurarse de que las Expresiones de parámetros sean iguales.

Estaba teniendo problemas con EF y el PredicateBuilder así que hice los míos 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<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> 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<Func<T, bool>>(body, p); } public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> 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<Func<T, bool>>(body, p); } }

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

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


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<Func<T, bool>> UpdateParameter<T>( Expression<Func<T, bool>> expr, ParameterExpression newParameter) { var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter); var body = visitor.Visit(expr.Body); return Expression.Lambda<Func<T, bool>>(body, newParameter); } [TestMethod] public void ExpressionText() { string text = "test"; Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text); Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text); Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]); var expr4 = Expression.Lambda<Func<Recording, bool>>( 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 de MIT mía: LinqExprHelper . Permite combinar expresiones lambda arbitrarias. Por lo general, las preguntas se hacen sobre la expresión de 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 se codifica solo una vez, y luego se reutiliza, gracias a la mini biblioteca LinqExprHelper .

public IQueryable<UbezpExt> UbezpFull { get { System.Linq.Expressions.Expression< Func<UBEZPIECZONY, UBEZP_ADRES, UBEZP_ADRES, UbezpExt>> 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<Func<UBEZPIECZONY, UbezpExt>>)expr2); } }

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

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

Lo que intenté 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 expresiones de ayuda, me vería obligado a crear una consulta completa de una sola vez.