Come comporre Espressione: selettore + predicato?

entity-framework expression-trees lambda linq

Domanda

Supponiamo di avere due classi

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

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

E due espressioni per selettore e predicatore

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

Ho bisogno di scrivere un metodo che restituisca espressioni composte come a

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

Nel mio esempio il risultato dovrebbe essere

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

ciò che è equivalente a

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

Grazie in anticipo.

Risposta accettata

Puoi provare quanto segue:

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

Ecco come usarlo:

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

Risposta popolare

Invocare queste espressioni lambda è sicuramente qualcosa che non vuoi fare. Quello che dovresti fare è riscrivere le espressioni. Avrai solo bisogno di un modo per associare i valori alle espressioni lambda come se li avessi invocati. Per fare ciò, riscrivi i corpi delle espressioni sostituendo i parametri con i valori a cui sei vincolante. Puoi usare questo SubstitutionVisitor per aiutare a farlo:

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

Date queste espressioni per esempio:

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

L'obiettivo è di riscriverlo efficacemente in modo che diventi così:

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

Per capire cosa sta succedendo qui, ecco una spiegazione linea per linea:

  1. Crea un nuovo parametro per il nuovo lambda che stiamo creando.

    entity => ...
    
  2. Dato il selettore, sostituire tutte le istanze entityA parametro originale entityA con la nostra nuova entity parametro dal corpo della lambda per ottenere la proprietà.

    entityA => entityA.EntityB
    // becomes
    entity.EntityB
    
  3. Dato il predicato, sostituire tutte le istanze entityB parametroB originale con entityB di proprietà ottenuta in entity.EntityB dal corpo della lambda per ottenere il corpo della nostra nuova lambda.

    entityB => entityB.IsDeleted && entityB.Name == "AAA"
    // becomes
    entity.EntityB.IsDeleted && entity.EntityB.Name == "AAA"
    
  4. Metti tutto insieme nella nuova lambda.

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


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché