2009-12-17 12 views
18

Ho un modulo con più campi su di esso (nome dell'azienda, codice postale, ecc.) Che consente a un utente di cercare società in un database. Se l'utente inserisce valori in più di un campo, devo cercare su tutti questi campi. Sto usando LINQ per interrogare il database.Come si combinano le espressioni LINQ in una?

Finora sono riuscito a scrivere una funzione che esaminerà il loro input e lo trasformerà in una lista di espressioni. Ora voglio trasformare quell'elenco in una singola espressione che posso poi eseguire tramite il provider LINQ.

Il mio primo tentativo è stata la seguente

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

Tuttavia, questo non riesce con un messaggio di eccezione sulla falsariga di "L'operatore binario e non è definita per ...". Qualcuno ha qualche idea su cosa devo fare per combinare queste espressioni?

MODIFICA: è stata corretta la riga in cui mi ero dimenticato di assegnare il risultato di un insieme di espressioni a una variabile. Grazie per avermelo fatto notare.

risposta

9

EDIT: la risposta di Jason è ora più completa della mia era in termini di espressioni tree roba, quindi ho rimosso quel bit. Tuttavia, volevo lasciare questo:

Suppongo che stiate usando questi per una clausola Where ... perché non basta chiamare Dove con ogni espressione a sua volta? Questo dovrebbe avere lo stesso effetto:

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

@Jon Skeet: 'combined' verrà digitato come' Expression'; è necessario fare un po 'di lavoro per restituirlo come un' espressione > '. – jason

+0

Sono d'accordo che il tuo primo codice sia più facile da capire, quindi farò di questo la risposta corretta. Comunque, in realtà userò il secondo frammento perché è esattamente ciò di cui ho bisogno - stavo rendendo le cose troppo complesse, grazie Jon. – gilles27

+1

Ironicamente stavo modificando mentre entrambi questi commenti sono stati scritti - ma siccome era questo secondo frammento che è stato usato, penso che lascerò così com'è :) –

22

È possibile utilizzare Enumerable.Aggregate combinato con Expression.AndAlso. Ecco una versione generica:

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

Il codice attuale è mai assegnando ad combined:

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

restituisce un nuovo Expression che è il risultato di bit a bit AND combined e expr ma non muta combined.

+0

+1 per una risposta tecnicamente ottima, grazie. Ho accettato Jon's come sembra più semplice e anche il suo uso di Where è in realtà ciò che dovrei fare. – gilles27

+1

@ gilles27: Sì, se lo stai usando solo per il predicato in una clausola 'Where', la risposta di Jon è la strada da percorrere. Se hai bisogno di una versione più generale, la mia versione ti aiuterà. :-) – jason

0

Qui abbiamo una domanda generale sulla combinazione di espressioni Linq. Ho una soluzione generale per questo problema. Fornirò una risposta per quanto riguarda il problema specifico pubblicato, anche se in questi casi non è assolutamente il modo di procedere. Ma quando soluzioni semplici falliscono nel tuo caso, puoi provare a usare questo approccio.

Per prima cosa è necessaria una libreria composta da 2 semplici funzioni. Usano System.Linq.Expressions.ExpressionVisitor per modificare dinamicamente le espressioni. La caratteristica fondamentale è unificare i parametri all'interno dell'espressione, in modo che 2 parametri con lo stesso nome siano stati resi identici (UnifyParametersByName). La parte rimanente sta sostituendo un parametro denominato con un'espressione data (ReplacePar). La libreria è disponibile con licenza MIT su github: LinqExprHelper, ma è possibile scrivere velocemente qualcosa da soli.

La libreria consente una sintassi abbastanza semplice per combinare espressioni complesse. Puoi unire espressioni lambda inline, che sono piacevoli da leggere, insieme alla creazione e alla composizione di espressioni dinamiche, che è molto capace.

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; 
    } 
Problemi correlati