Как составить выражение: селектор + предикат? - PullRequest
4 голосов
/ 10 января 2012

Предположим, что у нас есть два класса

public class EntityA
{
    public EntityB EntityB { get; set; }
}

public class EntityB
{
    public string Name { get; set; }
    public bool IsDeleted { get; set; }
}

И два выражения для селектора и предиктора

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

Мне нужно написать метод, который возвращает составное выражение как

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

В моем примере результат должен быть

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

что эквивалентно

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

Заранее спасибо.

Ответы [ 2 ]

3 голосов
/ 12 января 2012

Вызов этих лямбда-выражений, безусловно, то, что вы не хотите делать. Что вы должны делать, это переписывать выражения. Вам просто понадобится способ привязать значения к лямбда-выражениям, как если бы вы вызывали их. Для этого перепишите тела выражений, заменив параметры значениями, к которым вы привязываетесь. Вы можете использовать это SubstitutionVisitor, чтобы помочь сделать это:

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

Учитывая эти выражения, например:

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

Цель состоит в том, чтобы эффективно переписать его, чтобы оно выглядело так:

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

Чтобы понять, что здесь происходит, приведем построчное объяснение:

  1. Создайте новый параметр для новой лямбды, которую мы создаем.

    entity => ...
    
  2. Учитывая селектор, замените все экземпляры исходного параметра entityA на наш новый параметр entity из тела лямбды, чтобы получить свойство.

    entityA => entityA.EntityB
    // becomes
    entity.EntityB
    
  3. С учетом предиката замените все экземпляры исходного параметра entityB на ранее полученное свойство entity.EntityB из тела лямбды, чтобы получить тело нашего нового лямбды.

    entityB => entityB.IsDeleted && entityB.Name == "AAA"
    // becomes
    entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA"
    
  4. Соберите все вместе в новую лямбду.

    entity => entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA"
    
0 голосов
/ 10 января 2012

Вы можете попробовать следующее:

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

Вот как это использовать:

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