2009-12-17 81 views
18

我有一个表单上有多个字段(公司名称,邮编等),它允许用户搜索数据库中的公司。如果用户在多个字段中输入值,那么我需要搜索所有这些字段。我正在使用LINQ来查询数据库。如何将LINQ表达式合并为一个?

到目前为止,我已经设法编写一个函数,它将查看它们的输入并将其转换为表达式列表。我现在想把这个List变成一个单一的表达式,然后我可以通过LINQ提供程序执行。

我最初的尝试是如下

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

但是这个失败的线沿线的异常消息“二元运算符,而不是用于定义...”。有没有人有任何想法我需要做的结合这些表达式?

编辑:更正了我忘记将结果和表达式一起分配给一个变量的行。感谢您指出这些人。

回答

9

编辑:杰森的答案现在比我的表达树的东西更充分,所以我已经删除了那一点。然而,我想离开这个:

我假设你正在使用这些为Where条款...为什么不只是调用每个表达式依次在哪里?这应该有同样的效果:

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

@Jon Skeet:'combined'将被键入为'Expression';你需要做一些工作来将它作为'Expression >'返回。 – jason 2009-12-17 15:58:18

+0

我同意你的第一个代码更容易理解,所以我会做出这个正确的答案。但是我实际上要使用第二个片段,因为这正是我需要的 - 我让事情太复杂,谢谢乔恩。 – gilles27 2009-12-17 15:59:17

+1

具有讽刺意味的是,我在编辑这两条评论的同时也进行了编辑 - 但是因为这是使用的第二个片段,所以我认为我会保留原样:) – 2009-12-17 16:00:24

22

您可以使用Enumerable.AggregateExpression.AndAlso结合。这里有一个通用版本:

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

您当前的代码永远不会分配给combined

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

返回一个新Expression是按位安定combinedexpr的结果,但它并没有发生变异combined

+0

对于一个技术上很好的答案+1,谢谢。我已经接受了Jon,因为它看起来更简单,而且他使用Where实际上是我应该做的。 – gilles27 2009-12-17 16:00:39

+1

@ gilles27:是的,如果你只是将它用于Where子句中的谓词,那么Jon的答案就是要走的路。如果您需要更通用的版本,我的版本将帮助您。 :-) – jason 2009-12-17 16:03:01

0

这里我们有一个关于组合Linq表达式的一般问题。我有这个问题的一般解决方案。我会针对所发布的具体问题提供答案,但在这种情况下绝对不是这样。但是,如果简单的解决方案在您的情况下失败,您可以尝试使用这种方法。

首先,您需要一个由2个简单功能组成的库。他们使用System.Linq.Expressions.ExpressionVisitor来动态修改表达式。关键特性是将表达式中的参数统一起来,以便使具有相同名称的两个参数相同(UnifyParametersByName)。剩下的部分将用给定的表达式(ReplacePar)替换命名参数。该图书馆可在github上获得MIT许可证:LinqExprHelper,但您可以自行快速编写一些内容。

该库允许用于组合复杂表达式的相当简单的语法。您可以混合内联的lambda表达式,这些表达式非常适合阅读,以及动态表达式创建和组合,这非常有用。

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; 
    } 
相关问题