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<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、实体框架(所有版本)等等。
生成的谓词有点不优雅,但在大多数情况下,SQL 生成会减少噪音。如果过滤条件为空,您最终可能会在其中看到 WHERE 1 = 1
。
我们可以走得更远,解开提供的 lamdba 表达式,替换它们的参数并构建一个新的 lambda,但这是相当多的工作,收益相对较小。
【讨论】:
以上是关于IQueryable 如何构建查询? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
我正在寻找一种从字符串构建 IQueryable 查询的方法
IQueryable和IEnumerable以及AsEnumerable()和ToList()的区别
使用 EF 和 WebAPI,如何返回 ViewModel 并支持 IQueryable/OData? [复制]