C#Linq:将多个.Where()与* OR *子句组合

the*_*ode 13 .net c# linq where

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

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

SELECT * FROM TABLE WHERE (Field1 = X, Field2 = Y ... ) or (Field3 = Z)
Run Code Online (Sandbox Code Playgroud)

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

Object.Where(c => (c.Field1 == X && c.Field2 == Y) || (c.Field3 == Z))
Run Code Online (Sandbox Code Playgroud)

我不能使用这种方法,因为查询是通过使用多个.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.
Run Code Online (Sandbox Code Playgroud)

这对我来说就是那么复杂。所有这些.Where()调用都在构建与连接的Linq查询AND,这很好。

如何让他们使用括号执行并OR立即使用API 添加简单代码?

有没有一种方法可以将谓词保存在一些变量中,所以我可以进行如下操作:

Query = Query.Where(c => previousPredicates || c.Field3 == X)
Run Code Online (Sandbox Code Playgroud)

或如何解决该问题?

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

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

编辑 StackOverflow想让我说为什么我的问题与其他问题有所不同。好吧,事情是关于Parentheses。我不想.Where()用一个OR子句连接所有对象,我想让它们保留在一起,ANDOR在所有AND查询都加括号时添加另一个子句。

Mac*_*ski 8

如果要以编程方式构建查询并使其在SQL Server上执行,而不是获取所有记录并在内存中进行查询,则需要在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);       
    }
}
Run Code Online (Sandbox Code Playgroud)


Adr*_*ian 7

首先,创建一些辅助扩展方法来更容易地组合两个Func<T,bool>谓词:

 public static Func<T, bool> And<T>(this Func<T, bool> left, Func<T, bool> right) 
     => a => left(a) && right(a);

 public static Func<T, bool> Or<T>(this Func<T, bool> left, Func<T, bool> right)
     => a => left(a) || right(a);
Run Code Online (Sandbox Code Playgroud)

然后你可以使用它们来链接谓词:

var list = Enumerable.Range(1, 100);

Func<int, bool> predicate = v => true; // start with true since we chain ANDs first

predicate = predicate.And(v => v % 2 == 0); // numbers dividable by 2
predicate = predicate.And(v => v % 3 == 0); // numbers dividable by 3
predicate = predicate.Or(v => v % 31 == 0); // numbers dividable by 31

var result = list.Where(predicate);

foreach (var i in result)
    Console.WriteLine(i);
Run Code Online (Sandbox Code Playgroud)

输出:

6
12
18
24
30
31
36
42
48
54
60
62
66
72
78
84
90
93
96
Run Code Online (Sandbox Code Playgroud)

  • `IQueryable.Where` 接受 `Expression&lt;Func&lt;&gt;&gt;`,这是唯一可以转换为 SQL 的东西。`IEnumerable.Where` 接受 `Func&lt;&gt;`,因此将 `Func&lt;&gt;` 谓词传递给 EF / Linq2Sql 查询将导致在客户端获取整个表并使用谓词过滤。OP 特别想要一些可以转换为 SQL 的东西。 (6认同)
  • `IQueryable&lt;int&gt; result = list.AsQueryable().Where(predicate);` 是编译错误,“无法将类型 '`System.Collections.Generic.IEnumerable&lt;int&gt;`' 隐式转换为 '`System.Linq。 IQueryable&lt;int&gt;`'。存在显式转换(您是否缺少强制转换?)”。哪个是正确的。你在 GDB 上的代码是不同的,`.Where(v =&gt; predicate(v))`。这确实可以编译(因为它在技术上是一个 `Expression`,由于 `=&gt;` 由编译器为你构建),但它不会在数据上下文中工作,给出“Method 'System.Object DynamicInvoke(System.Object [])' 不支持转换为 SQL。” (3认同)
  • 有关生成谓词的通用方法,请参见 [LINQKIT](https://github.com/scottksmith95/LINQKit) (2认同)

ali*_*ost 5

您可以使用Expression这样的一步创建:

Expression<Func<Model, bool>> exp = (model => 
                                    ((model.Field1.HasValue && c.Field1 == X) &&
                                    (model.Field2.HasValue && c.Field2 == X)) ||
                                     model.Field3 == X
                                    )
Run Code Online (Sandbox Code Playgroud)

一旦定义了谓词,在查询中使用它们就非常容易。

var result = Query.AsQueryable().Where(exp)
Run Code Online (Sandbox Code Playgroud)

检查此要点中的代码: 我的要点网址

更新 1: 如果您必须使用步骤来创建表达式,您可以使用以下命令:

Expression<Func<Model, bool>> exp = c => true;
if (model.Field1.HasValue) 
{
    var prefix = exp.Compile();
    exp = c => prefix(c) && c.Field1 == X;
}

if (model.Field2.HasValue) 
{
    var prefix = exp.Compile();
    exp = c => prefix(c) && c.Field2 == X;
}

[...] like 20 more of these .Where() calls.
Run Code Online (Sandbox Code Playgroud)