首页 > 解决方案 > C# Linq:将多个 .Where() 与 *OR* 子句组合

问题描述

我一直在搜索我当前的问题,但我找不到解决该问题的真正答案。

我正在尝试构建一个生成以下 SQL 的 LINQ 查询:

SELECT * FROM TABLE WHERE (Field1 = X, Field2 = Y ... ) or (Field3 = Z)

在正常情况下,我会这样做:

Object.Where(c => (c.Field1 == X && c.Field2 == Y) || (c.Field3 == Z))

我不能使用这种方法,因为查询是通过使用多个.Where()调用构建的。

举个例子:

// This is a short example, the real world situation has 20 fields to check and they are all connected with an AND.
if (model.Field1.HasValue) 
{
    Query = Query.Where(c => c.Field1 == X)
}

if (model.Field2.HasValue) 
{
    Query = Query.Where(c => c.Field2 == X)
}

[...] like 20 more of these .Where() calls.

这就是我变得复杂的原因。所有这些.Where()调用都在构建一个与 连接的 Linq 查询AND,这很好。

我如何让他们使用括号执行并OR使用 API 添加一个简单的现在?

有没有办法将谓词保存在一些变量中,这样我就可以做出类似的事情:

Query = Query.Where(c => previousPredicates || c.Field3 == X)

或者如何解决这个问题?

我认为这个特定问题必须有一个好的解决方案,而且我不是唯一需要它的人,但我绝对不确定如何实现它。

PS:我无法真正删除多个.Where()调用,并且直接编写 SQL 也不是一种选择。

编辑 StackOverflow 想让我说出为什么我的问题与其他问题不同。嗯,事情是关于Parentheses。我不想.Where()用一个 OR 子句连接所有内容,我想在所有查询都被括号括起来时留下它们AND并添加另一个OR子句。AND

标签: c#.netlinqwhere

解决方案


如果您想以编程方式构建查询并让它在 SQL 服务器上执行,而不是获取所有记录并在内存中查询,则需要使用Expression类上的一组静态方法并使用这些方法构建查询。在您的示例中:

public class Query // this will contain your 20 fields you want to check against
{
    public int? Field1; public int? Field2; public int? Field3; public int Field4;
}

public class QueriedObject // this is the object representing the database table you're querying
{
    public int QueriedField;
}

public class Program
{
    public static void Main()
    {
        var queryable = new List<QueriedObject>().AsQueryable();
        var query = new Query { Field2 = 1, Field3 = 4, Field4 = 2 };

        // this represents the argument to your lambda expression
        var parameter = Expression.Parameter(typeof(QueriedObject), "qo");

        // this is the "qo.QueriedField" part of the resulting expression - we'll use it several times later
        var memberAccess = Expression.Field(parameter, "QueriedField");

        // start with a 1 == 1 comparison for easier building - 
        // you can just add further &&s to it without checking if it's the first in the chain
        var expr = Expression.Equal(Expression.Constant(1), Expression.Constant(1));

        // doesn't trigger, so you still have 1 == 1
        if (query.Field1.HasValue)
        {
            expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field1.Value)));
        }
        // 1 == 1 && qo.QueriedField == 1
        if (query.Field2.HasValue)
        {
            expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field2.Value)));
        }
        // 1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4
        if (query.Field3.HasValue)
        {
            expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field3.Value)));
        }

        // (1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4) || qo.QueriedField == 2
        expr = Expression.OrElse(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field4)));

        // now, we combine the lambda body with the parameter to create a lambda expression, which can be cast to Expression<Func<X, bool>>
        var lambda = (Expression<Func<QueriedObject, bool>>) Expression.Lambda(expr, parameter);

        // you can now do this, and the Where will be translated to an SQL query just as if you've written the expression manually
        var result = queryable.Where(lambda);       
    }
}

推荐阅读