IQueryable 如何构建查询?

How does the IQueryable builds the query?

我在 c# 中有这个项目,其中有几种可能的条件类型,假设它们是 title 和 level 只是简单地说。

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

案例:

所以我有

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

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

一般来说,在 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<int> 类型)时,将按顺序应用操作。第一个失败的值 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<T>我们可以简单地使用函数组合:

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<T> 来说并不那么简单,因为谓词类型是 Expression<Func<T, bool>> 类型的 LINQ Expression,我们无法从一个Func<T, bool>。相反,我们必须进行 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<T>:LinqToObjects、LinqToSQL、Entity Framework(所有版本)等等。


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

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