Comment compose Expression: sélecteur + prédicat?

entity-framework expression-trees lambda linq

Question

Supposons que nous ayons deux classes

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

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

Et deux expressions pour sélecteur et prédicateur

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

J'ai besoin d'écrire une méthode qui retourne une expression composée comme un

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

Dans mon exemple, le résultat devrait être

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

ce qui est équivalent à

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

Merci d'avance.

Réponse acceptée

Vous pouvez essayer ce qui suit:

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

Voici comment l'utiliser:

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

Réponse populaire

Invoquer ces expressions lambda est certainement quelque chose que vous ne voulez pas faire. Ce que vous devriez faire, c'est réécrire les expressions. Vous aurez juste besoin d'un moyen de lier des valeurs aux expressions lambda comme si vous les aviez appelées. Pour ce faire, réécrivez le corps des expressions en remplaçant les paramètres par les valeurs auxquelles vous êtes lié. Vous pouvez utiliser ce SubstitutionVisitor pour vous aider à faire cela:

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

Étant donné ces expressions par exemple:

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

Le but est de le réécrire efficacement pour qu'il devienne comme ceci:

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

Pour comprendre ce qui se passe ici, voici une explication ligne par ligne:

  1. Créez un nouveau paramètre pour le nouveau lambda que nous créons.

    entity => ...
    
  2. Étant donné le sélecteur, remplacez toutes les instances du paramètre original entityA par notre nouvelle entity paramètre du corps du lambda pour obtenir la propriété.

    entityA => entityA.EntityB
    // becomes
    entity.EntityB
    
  3. Étant donné le prédicat, remplacez toutes les instances du paramètre original entityB par la propriété entity.EntityB obtenue entity.EntityB partir du corps du lambda pour obtenir le corps de notre nouveau lambda.

    entityB => entityB.IsDeleted && entityB.Name == "AAA"
    // becomes
    entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA"
    
  4. Mettez tout cela dans le nouveau lambda.

    entity => entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA"
    


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow