我正在尝试使用linq编辑搜索工具,
我喜欢where子句中的过滤器(ItemNumber == X AND(StatementStatus == SatusA或StatementStatus == StatusB))
但是现在,它就像:
我喜欢where子句中的过滤器(ItemNumber == X AND StatementStatus == SatusA或StatementStatus == StatusB)
因为AND具有比OR更高的操作优先级,结果不是我想要的。 :) 你能帮忙吗?
using (var ctx = new MyContext()) {
Func<Statement, bool> filter = null;
if (!string.IsNullOrEmpty(request.ItemNumber))
filter = new Func<Statement, bool>(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
if (request.StatusA)
filter = filter == null ? new Func<Statement, bool>(s => s.StatementStatus == StatementStatusType.StatusA) :
filter.And(s => s.StatementStatus == StatementStatusType.StatusA);
if (request.StatusB)
filter = filter == null ? new Func<Statement, bool>(s => s.StatementStatus == StatementStatusType.StatusB) :
filter.Or(s => s.StatementStatus == StatementStatusType.StatusB);
var results = ctx.Statements
.Include("StatementDetails")
.Include("StatementDetails.Entry")
.Where(filter)
.Take(100)
.Select(s => new StatementSearchResultDTO{ ....
}
}
答案 0 :(得分:1)
这不是因为AND的优先级高于OR。现实中发生了什么:
var firstFilter = ...; // itemNumber
var secondFilter = ...; // statusA
var firstAndSecondFilter = firstFilter.And(secondFilter); // itemNumber && statusA
var thirdFilter = ...; // statusB
var endFilter = firstAndSecondFilter.Or(thirdFilter) // (itemNumber && statusA) || statusB.
问题 - 错误的控制流程。你必须做那样的事情:
var filterByA = ...;
var filterByB = ...;
var filterByAorB = filterByA.Or(filterByB);
var filterByNumber = ...;
var endFiler = filterByNumber.And(filterByAorB);
你的代码很糟糕,不仅因为它的工作原理错误,而且因为以这种方式编写代码很困难。理由:
StatusA
(查看你的三元运算符)和两个检查StatusB
你有太长的三元运算符和空检查。这很糟糕,因为你没有看到一般的图片,你的眼睛专注于语法问题。您可以为funcs编写extension method AndNullable。像这样:
static Func<T1, TOut> AndNullable<T1, TOut>(this Func<T1, TOut> firstFunc, Func<T1, TOut> secondFunc) {
if (firstFunc != null) {
if (secondFunc != null)
return firstFunc.And(secondFunc);
else
return firstFunc;
}
else {
if (secondFunc != null)
return secondFunc;
else
return null;
}
}
对于Or来说也一样。现在你的代码可以这样写:
Func<Statement, bool> filter = null;
if (request.StatusA)
filter = s => s.StatementStatus == StatementStatusType.StatusA;
if (request.StatusB)
filter = filter.OrNullable(s => s.StatementStatus == StatementStatusType.StatusB);
if (!string.IsNullOrEmpty(request.ItemNumber))
filter = filter.AndNullable(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
阅读更好。
您的过滤器是全局过滤器。对于很少的滤波器条件,全局滤波器的写入更简单,并且线的数量很少,但是理解滤波器会更复杂。用这种方式重写它:
Func<Statement, bool> filterByStatusA = null;
Func<Statement, bool> filterByStatusB = null;
if (request.StatusA)
filterByStatusA = s => s.StatementStatus == StatementStatusType.StatusA;
if (request.StatusB)
filterByStatusB = s => s.StatementStatus == StatementStatusType.StatusB;
Func<Statement, bool> filterByStatuses = filterByStatusA.OrNullable(filterByStatusB);
Func<Statement, bool> filterByItemNumber = null;
if (!string.IsNullOrEmpty(request.ItemNumber))
filterByItemNumber = s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber);
Func<Statement, bool> endFilter = filterByItemNumber.And(filterByStatuses);
好的,我们已经认为我们可以通过将它们组合为Func<..>
来编写过滤器,但我们仍有问题。
如果结果过滤器为空,我们会遇到什么问题?答案:ArgumentNullException
归因于documentation。我们必须考虑这个案例。
使用简单Func<...>
可以带来哪些其他问题?那么,你必须知道IEnumerable<T>
和IQueryable<T>
接口之间的区别。简单来说,IEnumerable上的所有操作都会导致所有元素的简单迭代(好吧,它是懒惰的,IEnumerable真的比IQueryable慢)。因此,例如,对于具有10000个对此过滤器不利的元素的集合中的Where(filter),Take(100),ToList()和400个良好元素的组合将导致迭代超过10100个元素。如果您为IQueryable编写了类似的代码,则过滤请求将在数据库服务器上发送,如果您在数据库上配置了索引,则此服务器将仅迭代~400(或1000,但不是10100)。那么代码中会发生什么。
var results = ctx.Statements // you are getting DbSet<Statement> that implements interface IQueryable<Statement> (and IQueryable<T> implements IEnumerable<T>)
.Include("StatementDetails") // still IQueryable<Statement>
.Include("StatementDetails.Entry") // still IQueryable<Statement>
.Where(filter) // Cuz your filter is Func<..> and there are no extension methods on IQueryable that accepts Func<...> as parameter, your IQueryable<Statement> casted automatically to IEnumerable<Statement>. Full collection will be loaded in your memory and only then filtered. That's bad
.Take(100) // IEnumerable<Statement>
.Select(s => new StatementSearchResultDTO { .... // IEnumerable<Statement> -> IEnumerable<StatementSearchResultDTO>
}
好。现在你明白了这个问题。因此,可以通过这种方式编写简单正确的代码:
using (var ctx = new MyContext()) {
results = ctx.Statements
.Include("StatementDetails")
.Include("StatementDetails.Entry")
.AsQueryable();
if (!string.IsNullOrEmpty(request.ItemNumber))
results = results.Where(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
if (request.StatusA) {
if (request.StatusB)
results = results.Where(s => s.StatementStatus == StatementStatusType.StatusA ||
s.StatementStatus == StatementStatusType.StatusA);
else
results = results.Where(s => s.StatementStatus == StatementStatusType.StatusA);
}
else {
if (request.StatusB) {
results = results.Where(s => s.StatementStatus == StatementStatusType.StatusB);
}
else {
// do nothing
}
}
results = .Take(100)
.Select(s => new StatementSearchResultDTO{ ....
};
// .. now you can you results.
}
是的,非常难看,但现在您的数据库解决了如何查找满足过滤器的语句。因此,这个请求很快就会出现。现在我们必须了解上面写的代码中发生了什么魔法。让我们比较两个代码示例:
results = results.Where(s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber));
而且:
Func<Statement, bool> filter = s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber);
results = results.Where(filter);
有什么区别?为什么第一个更快?答案:当编译器看到第一个代码时,它检查results
的类型是IQueryable<T>
和IEnumerable<T>
,以便括号内的条件可以具有类型Func<Statement, bool>
(已编译的函数)或{ {1}}(数据,可以在函数中编译)。编译器选择Expression<Func<Statement, bool>>
(为什么 - 真的不知道,只选择)。在第一个对象查询请求编译后不在C#语句中,而是在SQL语句中发送到服务器。由于存在索引,您的SQL服务器可以优化请求。
嗯,更好的方法 - 编写自己的表达式。编写自己的表达式有不同的方法,但有一种方法可以用不丑的语法编写它。您不能仅从另一个表达式调用一个表达式的问题 - 实体框架不支持该表达式,并且其他ORM不支持该问题。因此,我们可以使用Pete Montgomery的PredicateBuilder:link。然后在适合我们的表达式上编写两个简单的扩展。
Expression
对于And而言也一样。现在我们可以编写过滤器了:
public static Expression<Func<T, bool>> OrNullable<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
if (first != null && second != null)
return first.Compose(second, Expression.OrElse);
if (first != null)
return second;
if (second != null)
}
您可能遇到问题,因为.NET中的{
Expression<Func<Statement, bool>> filterByStatusA = null;
Expression<Func<Statement, bool>> filterByStatusB = null;
if (request.StatusA)
filterByStatusA = s => s.StatementStatus == StatementStatusType.StatusA;
if (request.StatusB)
filterByStatusB = s => s.StatementStatus == StatementStatusType.StatusB;
Expression<Func<Statement, bool>> filterByStatuses = filterByStatusA.OrNullable(filterByStatusB);
Expression<Func<Statement, bool>> filterByItemNumber = null;
if (!string.IsNullOrEmpty(request.ItemNumber))
filterByItemNumber = s => s.StatementDetails.Any(sd => sd.ItemNumber == request.ItemNumber);
Expression<Func<Statement, bool>> endFilter = filterByItemNumber.And(filterByStatuses);
requests = ...;
if (endFilter != null)
requests = requests.Where(endFilter);
}
中的类ExpressionVisitor
&lt; 4.0是密封的。您可以编写自己的ExpressionVisitor,也可以从this article复制它。
答案 1 :(得分:0)
好的,这是我解决它的方式:
filter.And(s => (request.StatusA && s.StatementStatus == StatementStatusType.StatusA) ||
(request.StatusB && s.StatementStatus == StatementStatusType.StautsB) ||
!(request.StatusA || request.StatusB)); //None selected = All selected
有任何意见吗?