The other day, I was tasked with the problem of selecting entities from IQueryable<T>
based on specific filter criteria that were only known at run-time. Basically, I had objects of different type with a set of properties.
Here is an example, of what we were trying to solve. We have two expressions that apply to different types and both return bool
.
Expression<Func<SomeClassToBeFiltered, bool>> entityExpression = e => e.Property1 >= 42;
Expression<Func<MagicFilter, bool>> filterExpression = f => f.IsPresent && f.ItemsAvailable > 0;
The goal is to combine these two, but obviously this will not compile (for several reasons):
var expr = entityExpression && filterExpression;
Let’s have a look how these classes would be have to be defined, so we can solve this. First, we need an interface to indicate, when we encounter a filterable entity:
public interface ICanBeFiltered { }
public class SomeClassToBeFiltered : ICanBeFiltered
{ public int Property1 { get; set; }
}
The filter would look then like this:
public interface IAmAFilter { }
public class MagicFilter : IAmAFilter
{
public MagicFilter(ICanBeFiltered entity) { /* ... */ }
public bool IsPresent { get; set; }
public int ItemsAvailable { get; set; }
}
Note, that we define a constructor on the filter, that accepts an entity of type ICanBeFiltered
. So in pseudo-code we could then do something like this:
e => e.Property1 >= 42 && e => (var f = new MagicFilte
r(e)).(f => IsPresent && f.ItemsAvailable > 0)
ExpressionVisitor
to the rescue! We need to rewrite the LambdaExpression
filterExpression
…
private class RewriteFilterTypeExpression<TFilterable, TFilterType> : ExpressionVisitor
where TFilterable : ICanBeFiltered
where TFilterType : IAmAFilter
{ }
… override VisitParameter
…
protected override Expression VisitParameter(ParameterExpression node) =>
ReferenceEquals(replacing, node)
? replacement
: base.VisitParameter(node);
… and override VisitLambda
…
protected override Expression VisitLambda(Expression node)
… and inside it define the creation of a new instance of the filter …
var newFilterType = Expression.New
( _filterTypeCtor,
replacement
);
… then we declare a variable to hold the instance of the filter …
var filterInstance = Expression.Variable(typeof(TFilterType), "filterTypeInstance");
… replace the inner filter parameter with the newly created filter instance parameter …
var visitor = new RewriteFilterTypeExpression(node.Parameters.Single(), filterInstance);
var replaced = visitor.Visit(node) as LambdaExpression;
… and then finally create a BlockExpression
to wrap this together and return the result of the original expression …
return Expression.Block
(
new[] { filterInstance },
Expression.Assign(filterInstance, newFilterType),
Expression.Condition
(
replaced.Body,
Expression.Constant(true),
Expression.Constant(false)
)
);
The expression block can now be logically combined with the first expression, as now both expect the same input type. Then the resulting expression would look similar to this:
Lambda1(Com.Example.SomeClassToBeFiltered $e)
{ $e.Property1 >= 42 && .Block(Com.Example.MagicFilter $filterTypeInstance)
{
$filterTypeInstance = .New Com.Example.MagicFilter($e);
.If (
(System.Boolean)$filterTypeInstance.IsPresent == True &&
(System.Boolean)$filterTypeInstance.ItemsAvailable > 0
) {
True
} .Else {
False
}
}
}
Once again, with the power of expressions, it is possible to achieve the seemingly impossible …