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