IQueryable 如何构建查询? [复制]

Posted

技术标签:

【中文标题】IQueryable 如何构建查询? [复制]【英文标题】:How does the IQueryable builds the query? [duplicate] 【发布时间】:2021-10-15 08:51:43 【问题描述】:

我在 c# 中有这个项目,我有几种可能的条件类型,让我们说它们是标题和级别,简单地说。

我正在尝试进行查询,但我不确定它是否以正确的方式构建。

案例:

标题已给出,我必须选择与该标题匹配的所有标题。 给定等级,与标题相同

所以我有

query = query.Where(x => x.Title == title);

以后

query = query.Where(x => x.Level == level)

我的问题是,这是如何翻译的? 像这样:

from * myTable WHERE Title = title and Level = level

或者像这样(这是我需要的方式)

from * myTable WHERE Title = title or Level = level

我必须做一些验证,我不能在同一个地方创建查询,

【问题讨论】:

您使用的是实体框架还是 Linq 2 Sql?有没有进行跟踪,看看生成的 sql 长什么样? @DavidL 我正在使用实体框架,我有一个断点,但对象有点奇怪,我看到了表达式,但说的是这样的 .Lambda #Lambda1<System.Func,我看不出它是否有和没有 对于 EF Core,您可以看到生成的查询 like this。 它是AND 而不是OR 【参考方案1】:

一般来说,对IEnumerable<T>IQueryable<T> 进行相同内容的相同系列操作应该会产生相同的输出...进行区分大小写的比较等等。

考虑以下几点:

int[] data = new[]  1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ;
var query = data.Where(i => i > 3);
query = query.Where(i => i < 8);

当您枚举query(类型为IEnumerable&lt;int&gt;)时,操作将按顺序应用。第一个 Where 失败的值不会被第二个评估,因此永远无法到达结果集:

Value First Where Second Where Output
1 Fail -- No
2 Fail -- No
3 Fail -- No
4 Pass Pass Yes
5 Pass Pass Yes
6 Pass Pass Yes
7 Pass Pass Yes
8 Pass Fail No
9 Pass Fail No
10 Pass Fail No

这相当于:

query = data.Where(i => i > 3 && i < 8);

等效的 SQL WHERE 将是:

WHERE i > 3 AND i < 8

虽然这适用于限制性过滤器(每个术语必须应用),但它对许可过滤器没有多大作用(任何术语可能适用) .相反,我们必须寻找其他方法来进行查询组合。

对于IEnumerable&lt;T&gt;,我们可以简单地使用函数组合:

string title = "A Title";
int level = 1;

Func<recordType, bool> predicate = x => x.Title == title;
predicate = x => predicate(x) || x.Level == level;

var query = data.Where(predicate);

不幸的是,这对于IQueryable&lt;T&gt; 来说并不那么简单,因为谓词类型是Expression&lt;Func&lt;T, bool&gt;&gt; 类型的LINQ Expression,我们无法从Func&lt;T, bool&gt; 创建合适的表达式树。相反,我们必须进行 lambda 表达式组合。

最简单的起点是实现一个OrElse 表达式合成器,它接受两个lambda 表达式并将它们作为OrElse 表达式的参数调用:

public static Expression<Func<T, bool>> OrElse<T>(Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)

    if (left is null)
        return right;
    
    if (right is null)
        return left;
    
    var parm = Expression.Parameter(typeof(T), "row");
    var result = Expression.Lambda<Func<T, bool>>
    (
        Expression.OrElse
        (
            Expression.Invoke(left, parm),
            Expression.Invoke(right, parm)
        ),
        parm
    );
    return result;

(这可能是一种扩展方法,但我认为设计明确允许目标的扩展是一种不好的形式。)

然后我们可以使用它从可选部分组成一个谓词表达式:

// 'Book' in this case is a placeholder for your record type.
Expression<Func<Book, bool>> MakePermissiveFilter(string title, int? level)

    Expression<Func<Book, bool>> result = null;
    
    if (!string.IsNullOrEmpty(title))
        result = OrElse(result, b => b.Title == title);
    
    if (level is not null)
        result = OrElse(result, b => b.Level == level);
    
    // if nothing selected return a default 'always true' predicate
    if (result is null)
        result = b => true;
    
    return result;

现在我们可以调用它来为您的Where 子句生成所需的过滤器表达式:

// 'query' previously defined with select and optional ordering
query = query.Where(MakePermissiveFilter(text, level));

这适用于任何兼容的IQueryable&lt;T&gt;:LinqToObjects、LinqToSQL、实体框架(所有版本)等等。


生成的谓词有点不优雅,但在大多数情况下,SQL 生成会减少噪音。如果过滤条件为空,您最终可能会在其中看到 WHERE 1 = 1

我们可以走得更远,解开提供的 lamdba 表达式,替换它们的参数并构建一个新的 lambda,但这是相当多的工作,收益相对较小。

【讨论】:

以上是关于IQueryable 如何构建查询? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

我正在寻找一种从字符串构建 IQueryable 查询的方法

IQueryable和IEnumerable以及AsEnumerable()和ToList()的区别

使用 EF 和 WebAPI,如何返回 ViewModel 并支持 IQueryable/OData? [复制]

在循环中使用 IQueryable 的实体框架 WHERE OR 查询

构建属于自己的ORM框架之二--IQueryable的奥秘

如何为 asp.net webapi 构建可重用的 .Net 客户端,包括 IQueryable 功能等