2009-12-17 6 views
18

J'ai un formulaire avec plusieurs champs (nom de l'entreprise, code postal, etc.) qui permet à un utilisateur de rechercher des entreprises dans une base de données. Si l'utilisateur entre des valeurs dans plus d'un champ, je dois effectuer une recherche sur tous ces champs. J'utilise LINQ pour interroger la base de données. Jusqu'à présent, j'ai réussi à écrire une fonction qui va regarder leur entrée et la transformer en une liste d'expressions. Je veux maintenant transformer cette liste en une seule expression que je peux ensuite exécuter via le fournisseur LINQ.Comment combiner des expressions LINQ en une?

Ma première tentative a été comme suit

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

Cependant cela échoue avec un message d'exception le long des lignes de « L'opérateur binaire ne définit pas pour ... ». Est-ce que quelqu'un a des idées sur ce que je dois faire pour combiner ces expressions?

EDIT: J'ai corrigé la ligne où j'avais oublié d'assigner le résultat de et d'associer les expressions à une variable. Merci d'avoir signalé cela.

Répondre

9

EDIT: La réponse de Jason est maintenant plus complète que la mienne en termes d'arborescence d'expressions, donc j'ai supprimé ce bit. Cependant, je voulais laisser ceci:

Je suppose que vous utilisez ceux-ci pour une clause Where ... pourquoi ne pas simplement appeler Où avec chaque expression à tour de rôle? Cela devrait avoir le même effet:

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

@Jon Skeet: 'combined' sera tapé comme' Expression'; vous devez faire un peu de travail pour le renvoyer en tant que 'Expression >'. – jason

+0

Je suis d'accord que votre premier code est plus facile à comprendre, donc je vais faire de cette bonne réponse. Cependant, je vais utiliser le second extrait car c'est exactement ce dont j'ai besoin - je rendais les choses beaucoup trop complexes, merci Jon. – gilles27

+1

Ironiquement, j'étais en train d'éditer pendant que ces deux commentaires étaient écrits - mais comme c'était ce deuxième extrait qui a été utilisé, je pense que je vais le laisser tel quel :) –

22

Vous pouvez utiliser Enumerable.Aggregate combiné avec Expression.AndAlso. Voici une version générique:

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

Votre code actuel est jamais assignant à combined:

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

retourne une nouvelle Expression qui est le résultat de bitwise anding combined et expr mais il ne mute pas combined.

+0

+1 pour une réponse techniquement excellente, merci. J'ai accepté Jon's car il semble plus simple et aussi son utilisation de Where est réellement ce que je devrais faire. – gilles27

+1

@ gilles27: Oui, si vous ne l'utilisez que pour le prédicat dans une clause 'Where', la réponse de Jon est la bonne façon de procéder. Si vous avez besoin d'une version plus générale, ma version vous aidera. :-) – jason

0

Ici, nous avons une question générale sur la combinaison des expressions Linq. J'ai une solution générale à ce problème. Je fournirai une réponse concernant le problème spécifique affiché, bien que ce ne soit certainement pas la façon de procéder dans de tels cas. Mais lorsque des solutions simples échouent dans votre cas, vous pouvez essayer d'utiliser cette approche.

D'abord vous avez besoin d'une bibliothèque composée de 2 fonctions simples. Ils utilisent System.Linq.Expressions.ExpressionVisitor pour modifier dynamiquement les expressions. La caractéristique clé est l'unification des paramètres dans l'expression, de sorte que 2 paramètres avec le même nom ont été rendus identiques (UnifyParametersByName). La partie restante remplace un paramètre nommé par une expression donnée (ReplacePar). La bibliothèque est disponible avec la licence MIT sur github: LinqExprHelper, mais vous pouvez rapidement écrire quelque chose par vous-même.

La bibliothèque permet une syntaxe assez simple pour combiner des expressions complexes. Vous pouvez mélanger des expressions lambda en ligne, ce qui est agréable à lire, avec la création dynamique d'expressions et la composition, ce qui est très efficace.

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