Combinaison d'arbres d'expressions multiples

.net c# entity-framework expression-trees

Question

Je reçois l'erreur suivante

Le paramètre 'p' n'était pas lié dans l'expression de requête LINQ to Entities spécifiée.

Je comprends le problème (la même instance de ParameterExpression doit être utilisée avec toutes les expressions de l’arborescence) et j’ai tenté d’utiliser les solutions que j’ai trouvées en ligne, mais sans succès.

C'est ma méthode

private void SeedEntity<TEntity>(DatabaseContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions) where TEntity : class
{
    Expression<Func<TEntity, bool>> allExpresions = null;

    var parameters = identifierExpressions.SelectMany(x => x.Parameters).GroupBy(x => x.Name).Select(p => p.First()).ToList();

    foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions)
    {
        Func<TEntity, object> vv = identifierExpression.Compile();
        object constant = vv(entity);

        ConstantExpression constExp = Expression.Constant(constant, typeof(object));
        BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp);
        Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, parameters);

        if (allExpresions == null)
        {
            allExpresions = equalExpression2;
        }
        else
        {
            BinaryExpression bin = Expression.And(allExpresions.Body, equalExpression2.Body);
            allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, parameters);
        }
    }

    TEntity existingEntity = null;
    if (allExpresions != null)
    {
        existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions);
    }

    if (existingEntity == null)
    {
        context.Set<TEntity>().Add(entity);
    }
    else
    {
        entity = existingEntity;
    }
}

Il génère une expression pour la recherche d'une entité basée sur un certain nombre de propriétés.

Cela fonctionne bien pour une seule expression, l'erreur ne se produit que lors de la transmission de plusieurs.

Appelé comme ça:

SeedEntity(context, ref e, p=> p.Name);//Works
SeedEntity(context, ref e, p=> p.Name, p=> p.Age);//Fails

Il génère quelque chose de similaire à ce que je réalise:

context.Set<TEntity>().FirstOrDefault(p=>p.Name == e.Name && p.Age == e.Age);

Remplacement de e.Name && e.Age par une ConstantExpression

Vous pouvez voir dans la méthode ci-dessus que je récupère tous les paramètres uniques et que je les stocke dans les parameters du haut, puis que j'utilise la même variable dans l'ensemble. Expression<Func<TEntity, bool>> passée dans le tableau params , c’est là que j’échoue.

J'ai essayé d'énumérer les expressions et d'utiliser la méthode .Update() transmettant les paramètres

J'ai aussi essayé une solution en utilisant ExpressionVisitor

public class ExpressionSubstitute : ExpressionVisitor
{
    public readonly Expression from, to;
    public ExpressionSubstitute(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        if (node == from) return to;
        return base.Visit(node);
    }
}

public static class ExpressionSubstituteExtentions
{
    public static Expression<Func<TEntity, TReturnType>> RewireLambdaExpression<TEntity, TReturnType>(Expression<Func<TEntity, TReturnType>> expression, ParameterExpression newLambdaParameter)
    {
        var newExp = new ExpressionSubstitute(expression.Parameters.Single(), newLambdaParameter).Visit(expression);
        return (Expression<Func<TEntity, TReturnType>>)newExp;
    }
}

Réponse acceptée

Tu es vraiment proche. Je ne vois pas le but de vos parameters variables. Les regrouper par nom est une erreur. Pourquoi ne pas simplement passer les paramètres de l'expression? Alors visitez si nécessaire. Votre code visiteur est correct.

    private static void SeedEntity<TEntity>(DbContext context, ref TEntity entity, params Expression<Func<TEntity, object>>[] identifierExpressions) 
        where TEntity : class
    {
        Expression<Func<TEntity, bool>> allExpresions = null;

        foreach (Expression<Func<TEntity, object>> identifierExpression in identifierExpressions)
        {
            Func<TEntity, object> vv = identifierExpression.Compile();
            object constant = vv(entity);

            ConstantExpression constExp = Expression.Constant(constant, typeof(object));
            BinaryExpression equalExpression1 = Expression.Equal(identifierExpression.Body, constExp);
            Expression<Func<TEntity, bool>> equalExpression2 = Expression.Lambda<Func<TEntity, bool>>(equalExpression1, identifierExpression.Parameters);

            if (allExpresions == null)
            {
                allExpresions = equalExpression2;
            }
            else
            {
                var visitor = new ExpressionSubstitute(allExpresions.Parameters[0], identifierExpression.Parameters[0]);
                var modifiedAll = (Expression<Func<TEntity,bool>>)visitor.Visit(allExpresions);
                BinaryExpression bin = Expression.And(modifiedAll.Body, equalExpression2.Body);
                allExpresions = Expression.Lambda<Func<TEntity, bool>>(bin, identifierExpression.Parameters);
            }
        }

        TEntity existingEntity = null;
        if (allExpresions != null)
        {
            existingEntity = context.Set<TEntity>().FirstOrDefault(allExpresions);
        }

        if (existingEntity == null)
        {
            context.Set<TEntity>().Add(entity);
        }
        else
        {
            entity = existingEntity;
        }
    }



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi