【问题标题】:How do I combine LINQ expressions into one?如何将 LINQ 表达式合并为一个?
【发布时间】:2016-10-17 04:05:26
【问题描述】:

我有一个包含多个字段(公司名称、邮政编码等)的表单,它允许用户在数据库中搜索公司。如果用户在多个字段中输入值,那么我需要搜索所有这些字段。我正在使用 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;
    }

但是,这会失败,并出现一条异常消息,类似于“二元运算符 And 未定义为...”。有没有人知道我需要做什么来组合这些表达式?

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

【问题讨论】:

  • 如果您只需要逻辑“和”,最好使用 AndAlso 以避免不必要的评估。

标签: linq


【解决方案1】:

您可以将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

【讨论】:

  • +1 以获得技术上出色的答案,谢谢。我接受了 Jon 的方法,因为它看起来更简单,而且他对 Where 的使用实际上是我应该做的。
  • @gilles27:是的,如果您仅将它用于Where 子句中的谓词,那么 Jon 的答案就是要走的路。如果您需要更通用的版本,我的版本将为您提供帮助。 :-)
  • 我试过了,但运行时错误“System.ArgumentException:为 lambda 声明提供的参数数量不正确”
【解决方案2】:

编辑:就表达式树的内容而言,Jason 的答案现在比我的更完整,所以我删除了那一点。但是,我想离开这个:

我假设您将这些用于Where 子句...为什么不轮流使用每个表达式调用 Where 呢?那应该有同样的效果:

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

【讨论】:

  • @Jon Skeet:combined 将被输入为Expression;您需要做一些工作才能将其作为Expression&lt;Func&lt;Company, bool&gt;&gt; 返回。
  • 我同意你的第一个代码更容易理解,所以我会把它作为正确的答案。但是我实际上将使用第二个 sn-p,因为这正是我所需要的 - 我让事情变得太复杂了,谢谢 Jon。
  • 具有讽刺意味的是,我在编写这两个 cmets 时正在编辑 - 但由于使用的是第二个 sn-p,我想我会保持原样:)
【解决方案3】:

这里我们有一个关于组合 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;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    • 2018-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多