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,但这是相当多的工作,收益相对较小。
我在 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,但这是相当多的工作,收益相对较小。