How to make an expression: selector + predicate?

Suppose 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 the selector and predicate

 Expression<Func<EntityA, EntityB>> selector = c => c.EntityB; Expression<Func<EntityB, bool>> predicate = c => c.IsDeleted && c.Name == "AAA"; 

I need to write a method that returns a compound expression like

 Expression<Func<TSource, bool>> Compose<TPropType>(Expression<Func<TSource, TPropType>> selector, Expression<Func<TPropType, bool>> predicator) { // Expression API ??? } 

In my example, the result should be

 Expression<Func<EntityA, bool>> exp = Compose(selector, predicate); 

which is equivalent

 Expression<Func<EntityA, bool>> exp = c => c.EntityB.IsDeleted && c.EntityB.Name == "AAA"; 

Thanks in advance.

+4
source share
2 answers

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

Calling these lambda expressions is something you don't want to do. What you have to do is rewrite the expressions. You just need a way to bind values ​​to lambda expressions, as if you were invoking them. To do this, rewrite the bodies of the expressions that replace the parameters with the values ​​to which you are bound. You can use this SubstitutionVisitor , to do this:

 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 that it becomes:

 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 is happening here, explain in turn:

  • Create a new parameter for the new lambda that we are creating.

     entity => ... 
  • Given the selector, replace all instances of the original entityA parameter entityA our new entity parameter from the lambda body to get the property.

     entityA => entityA.EntityB // becomes entity.EntityB 
  • Given the predicate, replace all instances of the original entityB parameter entityB the previously obtained entity.EntityB property from the lambda body to get the body of our new lambda.

     entityB => entityB.IsDeleted && entityB.Name == "AAA" // becomes entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA" 
  • Put it all together in a new lambda.

     entity => entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA" 
+2
source

Source: https://habr.com/ru/post/1390193/


All Articles