I know that if (includeFilter) query = query.Where (...)
gives the same result as query = query.Where (n => !includeFilter || <expression>)
.
But what if I have <expression1>
and <expression2>
and I want query.Where (n => <expression1> || <expression2>)
? Except that there are includeFilter1
and includeFilter2
too.
Simply query.Where (n => (!includeFilter1 || <expression1>) || (!includeFilter2 || <expression2>))
won't work, because in case includeFilter1
is false
, <expression2>
will have no effect.
Update:
I'm going to use it with IQueryable; is it OK? Is there a better solution?
Update 2:
I was wrong. What I needed was this:
var query1 = query.Where(n => !includeFilter1 || <expression1>)
var query2 = query.Where(n => !includeFilter2 || <expression2>)
if (includeFilter1 && includeFilter2)
query = query1.Union(query2);
else if (includeFilter1)
query = query1;
else if (includeFilter2)
query = query2;
I couldn't come up with a more elegant way.
CodePudding user response:
In general .Union()
is best reserved for cases where you can't represent the logic of the filter in a single query, or when the data is being drawn from different sources in each part of the query. Each .Union()
call most likely results in a separate query execution, which means the query optimizer has little to no chance to produce a reasonable execution plan.
Using .Where(n => !includeFilter || <expression>)
is also less that optimal on the face of it. The expression tree will close over the includeFilter
variable and may not be optimized particularly well depending on the ORM you're using. Best to avoid that where possible.
There are several options for this kind of query composition. The simplest, which works fine for a couple of entries, is this:
if (includeFilter1)
{
if (includeFilter2)
query = query.Where(q => <expression1> || <expression2>);
else
query = query.Where(q => <expression1>);
}
else if (includeFilter2)
query = query.Where(q => <expression2>);
Not very elegant perhaps, but it works just fine and only takes a few lines of code to implement... if <expression1>
and <expression2>
are known at compile time and aren't parameters to the method.
What I think you're looking for is a more generic way to handle filter expression composition. LINQ has some rather interesting expression handling and while expressions can be scary at first, once you get the hang of them you can do some quite cool things.
For instance, here's an extension (and a supporting class) that will take two predicates on the same type and join them with an Or
:
using System.Linq.Expressions
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
// Useful for chained or conditional composition:
if (left is null)
return right;
if (right is null)
return left;
// Just in case:
if (ReferenceEquals(left, right))
return left;
// change right parameter references to match left
var rightBody = ReplaceVisitor.Replace(right.Body, right.Parameters[0], left.Parameters[0]);
// build 'or' expression
var body = Expression.OrElse(left.Body, right.Body);
// Create lambda (function) for predicate
var result = Expression.Lambda<Func<T, bool>>(body, left.Parameters[0]);
return result;
}
}
// Helper class, replaces expression instances
// Used to get the right parameters in composed expressions.
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression _from;
private readonly Expression _to;
private ReplaceVisitor(Expression from, Expression to)
{
_from = from;
_to = to;
}
public override Expression Visit(Expression e)
=> ReferenceEquals(e, _from) ? _to : base.Visit(e);
public static T Replace<T>(T expression, Expression from, Expression to)
where T : Expression
{
var visitor = new ReplaceVisitor(from, to);
return (T)visitor.Visit(expression);
}
}
(You can create a similar .And()
extension using the AndAlso
expression.)
Using that you can directly merge your filter expresions with an Or
like so:
public IQueryable<T> ApplyFilters<T>(IQueryable<T> query,
bool useFilter1, Expression<Func<T, bool>> predicate1,
bool useFilter2, Expression<Func<T, bool>> predicate2
)
{
Expression<Func<T, bool>> predicate = null;
if (useFilter1)
predicate = predicate.Or(predicate1);
if (useFilter2)
predicate = predicate.Or(predicate2);
return predicate is null ? query : query.Where(predicate);
}
Or perhaps this:
public IQuerable<T> AnyOf<T>(IQueryable<T> query, params Expression<Func<T, bool>>[] filters)
{
Expression<Func<T, bool>> predicate = null;
foreach (var filter in filters)
predicate = predicate.Or(filter);
return predicate is null ? query : query.Where(predicate);
}
Try it out, see if it fits your use case.
CodePudding user response:
I think you need
query.Where(n => (!includeFilter1 || <expression1>) &&
(!includeFilter2 || <expression2>))