【问题标题】:C# Linq: Combine multiple .Where() with an *OR* clauseC# Linq:将多个 .Where() 与 *OR* 子句组合
【发布时间】:2023-03-06 21:36:01
【问题描述】:

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

我正在尝试构建一个生成以下 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() 调用都在构建一个与AND 连接的Linq Query,这很好。

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

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

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

或者如何解决这个问题?

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

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

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

【问题讨论】:

  • 不,它不是重复的。虽然另一个链接很有帮助,但它并没有真正解释如何实现括号,因为它只解释了如何用 `OR ` 替换所有 `AND ` 连接。
  • 是的,它是重复的。在Jon Skeet's code 中,someOtherCondition 是可能包含多个ANDs 的表达式。然后这些AND 块逐渐ORed 在一起。
  • 另一个答案告诉你如何组合谓词。只需将|| 替换为&&。然后你就可以做你想做的事了Query = Query.Where(c => previousPredicates || c.Field3 == X)
  • 假设字段可以为空,示例可以缩短为:Query = Query.Where(c => c.Field1 ?? X == X && c.Field2 ?? X == X)。如果是实体框架查询,加标签,因为有些情况不适用

标签: c# .net linq where


【解决方案1】:

如果您想以编程方式构建查询并让它在您的 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);       
    }
}

【讨论】:

  • 是的,这是目前为止唯一可行的答案,尽管使用起来很不方便。
【解决方案2】:

首先,创建一些辅助扩展方法,以便更轻松地组合两个 Func&lt;T,bool&gt; 谓词:

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

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

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

输出:

6
12
18
24
30
31
36
42
48
54
60
62
66
72
78
84
90
93
96

【讨论】:

  • IQueryable.Where 接受Expression&lt;Func&lt;&gt;&gt;,这是唯一可以转换为 SQL 的东西。 IEnumerable.Where 接受 Func&lt;&gt;,因此将 Func&lt;&gt; 谓词传递给 EF / Linq2Sql 查询将导致在客户端上获取整个表并使用谓词进行过滤。 OP 特别想要转换为 SQL 的东西。
  • 请参阅LINQKIT 了解生成谓词的通用方式——除此之外
  • 这当然适用于IQueryable&lt;&gt;。仔细查看“工作”示例中var result类型 - 它是IEnumerable&lt;&gt;,而不是IQueryable&lt;&gt;。即使您制作了最终的谓词表达式,在内部使用委托也会使其不可翻译。
  • 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; 而由编译器为您构建),但它不会在数据上下文中工作,给出“方法'System.Object DynamicInvoke(System.Object [ ])' 没有支持的 SQL 转换。”
  • @Adrian 正确,Jon Skeet has confirmed 认为它不起作用。在我看到他使用 Func&lt;&gt; 而不是 Expression&lt;Func&lt;&gt;&gt; 之前,我已经指出了它。
【解决方案3】:

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

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

一旦定义了谓词,就可以很容易地在查询中使用它们。

var result = Query.AsQueryable().Where(exp)

检查此要点中的代码: my gist url

更新 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.

【讨论】:

【解决方案4】:

好的,你对 linq 有自己的回答。

让我介绍一种使用Dynamic.linq的不同方法

// You could build a Where string that can be converted to linq.
// and do if sats and append your where sats string. as the example below
var query = "c => (c.Field1 == \" a \" && c.Field2 == Y) || (c.Field3 == \" b \")";
var indicator = query.Split('.').First(); // the indicator eg c
   // assume TABLE is the name of the class
var p = Expression.Parameter(typeof(TABLE), indicator);
var e = DynamicExpression.ParseLambda(new[] { p }, null, query);

// and simple execute the expression 
var items = Object.Where(e);

【讨论】:

    猜你喜欢
    • 2021-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多