I\'ve looked into many generic linq filtering questions and their answers here in SO but none of them satisfy my needs so I thought I should create a question.
I\'ve
So to solve this problem we need a few puzzle pieces first. The first puzzle piece is a method that can take an expression that computes a value, and then another expression that computes a new value taking the same type the first returns, and creates a new expression that represents the result of passing the result of the first function as the parameter to the second. This allows us to Compose expressions:
public static Expression>
Compose(
this Expression> first,
Expression> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda>(newSecond, param);
}
This relies on the following tool to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
We'll also need a tool to help us OR two predicate expressions together:
public static class PredicateBuilder
{
public static Expression> True() { return f => true; }
public static Expression> False() { return f => false; }
public static Expression> Or(
this Expression> expr1,
Expression> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression> And(
this Expression> expr1,
Expression> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
Now that we have this we can use Compose on each property selector to map it from the property results to whether or not that property value is non-null and contains the search term. We can then OR all of those predicates together to get a filter for your query:
public static IQueryable Match(
IQueryable data,
string searchTerm,
IEnumerable>> filterProperties)
{
var predicates = filterProperties.Select(selector =>
selector.Compose(value =>
value != null && value.Contains(searchTerm)));
var filter = predicates.Aggregate(
PredicateBuilder.False(),
(aggregate, next) => aggregate.Or(next));
return data.Where(filter);
}