Возможно, вы могли бы использовать кортеж значения пар тест-функция и выражение:
ProductQuery filter = ... // initialize here
var exprs = new List<(Func<ProductQuery, object>, Expression<Func<Product, bool>>)>() {
(f => f.CategoryId, p => p.CategoryId == filter.CategoryId),
(f => f.MinPrice, p => p.Price >= filter.MinPrice),
(f => f.MaxPrice, p => p.Price <= filter.MaxPrice)
};
foreach (var (test, expr) in exprs) {
if (test(filter) != null) {
products = products.Where(expr);
}
}
Вы можете пойти еще дальше, проанализировав дерево выражений (например, p => p.CategoryId == filter.CategoryId
) и увидев, какой элемент (ы) из filter
используется (например, filter.CategoryId
). Тогда вы можете применить условие, только если этот член имеет значение:
ProductQuery filter = ... // initialize here
var exprs = new List<Expression<Func<Product, bool>>>() {
p => p.CategoryId == filter.CategoryId,
p => p.Price >= filter.MinPrice,
p => p.Price <= filter.MaxPrice
};
foreach (var expr in exprs) {
var pi = ((expr.Body as BinaryExpression)
.Right as MemberExpression)
.Member as PropertyInfo;
if (pi.GetValue(filter) != null) {
products = products.Where(expr);
}
}
Таким образом, вы можете избежать определения нулевой проверки.
Код, который анализирует выражения, вероятно, должен быть более гибким - что, если свойство фильтра будет первым в выражении? Что, если где-то есть конверсии?
Я бы также предложил инкапсулировать логику построения выражения одного фильтра как свойство ProductQuery
:
public Expression<Product, bool> Filter => {
get {
// implementation at end of answer
}
}
, который можно затем вызывать без каких-либо петель:
products = products.Where(filter.Filter);
Вы можете реализовать это самостоятельно, но я настоятельно рекомендую использовать LINQKit PredicateBuilder :
public Expression<Func<Product, bool>> Filter {
get {
var exprs = new List<Expression<Func<Product, bool>>>() {
p => p.CategoryId == this.CategoryId,
p => p.Price >= this.MinPrice,
p => p.Price <= this.MaxPrice
}.Where(expr => {
PropertyInfo mi = ((expr.Body as BinaryExpression)
.Right as MemberExpression)
.Member as PropertyInfo;
return mi.GetValue(this) != null;
});
var predicate = PredicateBuilder.True<Product>();
foreach (var expr in exprs) {
predicate = predicate.And(expr);
}
return predicate;
}
}