Reputation: 63
Assume that we have two classes
public class EntityA
{
public EntityB EntityB { get; set; }
}
public class EntityB
{
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
And two expressions for selector and predicator
Expression<Func<EntityA, EntityB>> selector = c => c.EntityB;
Expression<Func<EntityB, bool>> predicate = c => c.IsDeleted && c.Name == "AAA";
I need write a method that returns composed expression like a
Expression<Func<TSource, bool>> Compose<TPropType>(Expression<Func<TSource, TPropType>> selector, Expression<Func<TPropType, bool>> predicator)
{
// Expression API ???
}
In my example result should be
Expression<Func<EntityA, bool>> exp = Compose(selector, predicate);
what is equivalent to
Expression<Func<EntityA, bool>> exp = c => c.EntityB.IsDeleted && c.EntityB.Name == "AAA";
Upvotes: 5
Views: 1718
Reputation: 134811
Invoking these lambda expressions is certainly something you do not want to be doing. What you should be doing is rewriting the expressions. You'll just need a way to bind values to the lambda expressions as if you invoked them. To do that, rewrite the bodies of the expressions replacing the parameters with the values you're binding to. You can use this SubstitutionVisitor
to help do that:
public class SubstitutionVisitor : ExpressionVisitor
{
public Expression OldExpr { get; set; }
public Expression NewExpr { get; set; }
public override Expression Visit(Expression node)
{
return (node == OldExpr) ? NewExpr : base.Visit(node);
}
}
Given these expressions for example:
Expression<Func<EntityA, EntityB>> selector =
entityA => entityA.EntityB;
Expression<Func<EntityB, bool>> predicate =
entityB => entityB.IsDeleted && entityB.Name == "AAA";
The goal is to effectively rewrite it so it becomes like this:
Expression<Func<EntityA, bool>> composed =
entity => entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA";
static Expression<Func<TSource, bool>> Compose<TSource, TProp>(
Expression<Func<TSource, TProp>> selector,
Expression<Func<TProp, bool>> predicate)
{
var parameter = Expression.Parameter(typeof(TSource), "entity");
var property = new SubstitutionVisitor
{
OldExpr = selector.Parameters.Single(),
NewExpr = parameter,
}.Visit(selector.Body);
var body = new SubstitutionVisitor
{
OldExpr = predicate.Parameters.Single(),
NewExpr = property,
}.Visit(predicate.Body);
return Expression.Lambda<Func<TSource, bool>>(body, parameter);
}
To understand what's going on here, here's a line-by-line explanation:
Create a new parameter for the new lambda we're creating.
entity => ...
Given the selector, replace all instances of the original parameter entityA
with our new parameter entity
from the body of the lambda to obtain the property.
entityA => entityA.EntityB
// becomes
entity.EntityB
Given the predicate, replace all instances of the original parameter entityB
with previously obtained property entity.EntityB
from the body of the lambda to obtain the body of our new lambda.
entityB => entityB.IsDeleted && entityB.Name == "AAA"
// becomes
entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA"
Put it all together into the new lambda.
entity => entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA"
F# port, in case people fin'd it more legible:
static private Compose<'T, 'I>(selector: Expression<Func<'T, 'I>>, predicate: Expression<Func<'I, bool>>) =
let expand find replace =
{ new ExpressionVisitor() with
override _.Visit node =
if node = find then replace
else base.Visit node }
let param = Expression.Parameter(typeof<'T>, "x")
let prop = (expand selector.Parameters[0] param).Visit(selector.Body)
let body = (expand predicate.Parameters[0] prop).Visit(predicate.Body)
Expression.Lambda<Func<'T, bool>>(body, param)
Upvotes: 4
Reputation: 8558
You can try the following:
static Expression<Func<TSource, bool>> Compose<TSource, TPropType>(
Expression<Func<TSource, TPropType>> selector,
Expression<Func<TPropType, bool>> predicator)
{
ParameterExpression param = Expression.Parameter(typeof(TSource), "sourceObj");
Expression invokedSelector = Expression.Invoke(selector, new Expression[] { param });
Expression invokedPredicate = Expression.Invoke(predicator, new[] { invokedSelector });
return Expression.Lambda<Func<TSource, bool>>(invokedPredicate, new[] { param });
}
Here's how to use it:
static void Main()
{
Expression<Func<EntityA, EntityB>> selector = c => c.EntityB;
Expression<Func<EntityB, bool>> predicate = c => c.IsDeleted && c.Name == "AAA";
Expression<Func<EntityA, bool>> exp = Compose(selector, predicate);
System.Console.WriteLine(exp.Compile()(new EntityA()));
}
Upvotes: 0