如何将LINQ表达式组合成一个表达式?

beq87vna  于 2022-12-06  发布在  其他
关注(0)|答案(4)|浏览(180)

我有一个表单,上面有多个字段(公司名称、邮政编码等),允许用户在数据库中搜索公司。如果用户在多个字段中输入值,那么我需要搜索所有这些字段。我正在使用LINQ查询数据库。
到目前为止,我已经成功地编写了一个函数,该函数将查看它们的输入并将其转换为表达式列表。现在,我想将该列表转换为一个表达式,然后可以通过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没有为...定义"。有人知道我需要做什么来合并这些表达式吗?
编辑:更正了我忘记将表达式的结果赋值给变量的地方。谢谢你指出这一点。

oo7oh9g9

oo7oh9g91#

您可以将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的比特AND运算结果,但不会变异combined

gupuwyp2

gupuwyp22#

编辑:Jason的答案在表达式树方面比我的答案更全面,所以我删除了这一部分。但是,我想留下以下内容:
我假设您要在Where子句中使用这些表达式......为什么不对每个表达式依次调用Where呢?这应该具有相同的效果:

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

ldxq2e6h3#

这里我们有一个关于组合Linq表达式的一般性问题。我有一个解决这个问题的一般性方法。我将提供一个关于所发布的具体问题的答案,尽管在这种情况下这肯定不是一个正确的方法。但是当简单的解决方案在你的情况下失败时,你可以尝试使用这种方法。
首先你需要一个由两个简单函数组成的库。它们使用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;
    }
6kkfgxo0

6kkfgxo04#

假设你有两个表达式e1和e2,你可以这样尝试:

var combineBody = Expression.AndAlso(e1.Body, Expression.Invoke(e2, e1.Parameters[0]));
var finalExpression = Expression.Lambda<Func<TestClass, bool>>(combineBody, e1.Parameters).Compile();

相关问题