發生這種情況不是因爲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);
而且你的代碼是壞的,不只是因爲它的工作原理錯誤的,但因爲它很難在這樣的風格來編寫代碼。理由:
- 此代碼不符合DRY principle。你有兩個相同的lambda表達式,檢查
StatusA
(看看你的三元運算符)和兩個相同的lambda表達式,檢查StatusB
你有太長的三元運算符與空檢查。這很糟糕,因爲你沒有看到一般圖片,你的眼睛集中在語法問題上。你可以寫和extension method AndNullable for funcs。像這樣:
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);
好了,我們已經outthinked我們如何能夠通過將它們組合爲Func<..>
寫的過濾器,但我們還是有問題。
如果結果過濾器爲空,我們會得到什麼問題?答案:ArgumentNullException
由於documentation。我們必須考慮這個案例。
使用簡單的Func<...>
可以得到什麼問題?那麼,你必須知道IEnumerable<T>
和IQueryable<T>
接口之間的區別。簡單地說,IEnumerable上的所有操作都會導致對所有元素的簡單迭代(呃,它很懶,IEnumerable真的比IQueryable慢)。因此,例如,對具有10000個對該過濾器有害的元素和400個良好的元素的集合的Where(過濾器),Take(100),ToList()組合會導致迭代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>
(編譯函數)或Expression<Func<Statement, bool>>
(數據,可以在函數中編譯)。編譯器選擇Expression
(爲什麼 - 真的不知道,只是選擇)。第一個對象查詢的請求不是在C#語句中編譯,而是在SQL語句中編譯併發送到服務器。由於存在索引,您的SQL服務器可以優化請求。
那麼,更好的方法 - 編寫自己的表達式。有不同的方式來編寫自己的表達式,但有一種方法可以用不難看的語法來編寫它。不能僅從另一個表達式調用一個表達式的問題 - 實體框架不支持並且不能被另一個ORM支持。所以,我們可以使用Pete Montgomery的PredicateBuilder:link。然後在適合我們的表達式上寫兩個簡單的擴展。
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)
}
而且和And相同。現在我們可以寫我們的過濾器:
{
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
在PredicateBuilder
在.NET 4.0 <密封。您可以編寫自己的ExpressionVisitor或從this article複製它。
你喜歡我的回答?或者你仍然有一些問題? –