qmicron
qmicron

Reputation: 63

How compose Expression: selector + predicate?

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

Answers (2)

Jeff Mercado
Jeff Mercado

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:

  1. Create a new parameter for the new lambda we're creating.

     entity => ...
    
  2. 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
    
  3. 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"
    
  4. 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

Igor Korkhov
Igor Korkhov

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

Related Questions