How compose Expression: selector + predicate?

entity-framework expression-trees lambda linq

Question

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";

Thanks in advance.

Accepted Answer

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()));
}

Popular Answer

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"
    



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why