2009-12-17 8 views
18

Tengo un formulario con múltiples campos (nombre de la empresa, código postal, etc.) que permite a un usuario buscar compañías en una base de datos. Si el usuario ingresa valores en más de un campo, entonces necesito buscar en todos esos campos. Estoy usando LINQ para consultar la base de datos.¿Cómo combino las expresiones LINQ en una sola?

Hasta ahora he logrado escribir una función que verá su entrada y la convertirá en una lista de expresiones. Ahora quiero convertir esa lista en una sola expresión que luego puedo ejecutar a través del proveedor de LINQ.

Mi primer intento fue el siguiente

private Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions) 
    { 
     if (expressions.Count == 0) 
     { 
      return null; 
     } 
     if (expressions.Count == 1) 
     { 
      return expressions[0]; 
     } 
     Expression<Func<Company, bool>> combined = expressions[0]; 
     expressions.Skip(1).ToList().ForEach(expr => combined = Expression.And(combined, expr)); 
     return combined; 
    } 

Sin embargo, esta falla con un mensaje de excepción en la línea de "El operador binario y no se define por ...". ¿Alguien tiene alguna idea de lo que debo hacer para combinar estas expresiones?

EDIT: Corregí la línea donde había olvidado asignar el resultado de juntar las expresiones a una variable. Gracias por señalar eso amigos.

Respuesta

9

EDITAR: La respuesta de Jason ahora es más completa que la mía en términos de expresión de árbol, así que he eliminado ese bit. Sin embargo, quería dejar esto:

Supongo que los está usando para una cláusula Where ... ¿por qué no simplemente llamar a Where con cada expresión por turno? Eso debería tener el mismo efecto:

var query = ...; 
foreach (var condition in conditions) 
{ 
    query = query.Where(condition); 
} 
+1

@Jon Skeet: 'combined' se escribirá como 'Expresión'; necesita hacer algún trabajo para devolverlo como 'Expression >'. – jason

+0

Acepto que su primer código es más fácil de entender, por lo que haré de esto la respuesta correcta. Sin embargo, en realidad voy a utilizar el segundo fragmento, ya que es exactamente lo que necesito. Estaba haciendo las cosas demasiado complejas, gracias Jon. – gilles27

+1

Irónicamente estaba editando mientras escribía estos dos comentarios, pero como se utilizó este segundo fragmento, creo que lo dejo tal como está :) –

22

Usted puede usar combinado con Enumerable.AggregateExpression.AndAlso. Aquí hay una versión genérica:

Expression<Func<T, bool>> AndAll<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions) { 

    if(expressions == null) { 
     throw new ArgumentNullException("expressions"); 
    } 
    if(expressions.Count() == 0) { 
     return t => true; 
    } 
    Type delegateType = typeof(Func<,>) 
          .GetGenericTypeDefinition() 
          .MakeGenericType(new[] { 
           typeof(T), 
           typeof(bool) 
          } 
         ); 
    var combined = expressions 
         .Cast<Expression>() 
         .Aggregate((e1, e2) => Expression.AndAlso(e1, e2)); 
    return (Expression<Func<T,bool>>)Expression.Lambda(delegateType, combined); 
} 

su código actual nunca se asigna a combined:

expr => Expression.And(combined, expr); 

devuelve una nueva Expression que es el resultado de la operación AND bit a bit combined y expr pero no muta combined.

+0

+1 para una respuesta técnicamente excelente, gracias. Acepté el de Jon, ya que parece más simple, y también su uso de Dónde está realmente lo que debería estar haciendo. – gilles27

+1

@ gilles27: Sí, si solo lo está usando para el predicado en una cláusula 'Where', entonces la respuesta de Jon es el camino a seguir. Si alguna vez necesitas una versión más general, mi versión te ayudará. :-) – jason

0

Aquí tenemos una pregunta general sobre la combinación de expresiones Linq. Tengo una solución general para este problema. Proporcionaré una respuesta sobre el problema específico publicado, aunque definitivamente no es el camino a seguir en tales casos. Pero cuando las soluciones simples fallan en su caso, puede intentar usar este enfoque.

Primero necesita una biblioteca que consta de 2 funciones simples. Usan System.Linq.Expressions.ExpressionVisitor para modificar expresiones dinámicamente. La característica clave es la unificación de parámetros dentro de la expresión, de modo que 2 parámetros con el mismo nombre se hicieron idénticos (UnifyParametersByName). La parte restante reemplaza un parámetro nombrado con la expresión dada (ReplacePar). La biblioteca está disponible con la licencia de MIT en github: LinqExprHelper, pero puede escribir rápidamente algo por su cuenta.

La biblioteca permite una sintaxis bastante simple para combinar expresiones complejas. Puede mezclar expresiones lambda en línea, que son agradables de leer, junto con la creación y composición de expresiones dinámicas, que es muy capaz.

private static Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions) 
    { 
     if (expressions.Count == 0) 
     { 
      return null; 
     } 

     // Prepare a master expression, used to combine other 
     // expressions. It needs more input parameters, they will 
     // be reduced later. 
     // There is a small inconvenience here: you have to use 
     // the same name "c" for the parameter in your input 
     // expressions. But it may be all done in a smarter way. 
     Expression <Func<Company, bool, bool, bool>> combiningExpr = 
      (c, expr1, expr2) => expr1 && expr2; 

     LambdaExpression combined = expressions[0]; 
     foreach (var expr in expressions.Skip(1)) 
     { 
      // ReplacePar comes from the library, it's an extension 
      // requiring `using LinqExprHelper`. 
      combined = combiningExpr 
       .ReplacePar("expr1", combined.Body) 
       .ReplacePar("expr2", expr.Body); 
     } 
     return (Expression<Func<Company, bool>>)combined; 
    } 
Cuestiones relacionadas