合并多个表达式树

6

我遇到了以下错误

在指定的LINQ to Entities查询表达式中,参数“p”未绑定。

我理解这个问题(树中所有表达式应该使用同一个ParameterExpression实例),并尝试使用我在网上找到的解决方案,但没有成功。

这是我的方法:

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

它根据一些属性生成实体查找表达式。

单个表达式运行良好,仅在传入多个表达式时出现错误。

调用方式如下:

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

它生成的结果类似于我执行以下操作:
context.Set<TEntity>().FirstOrDefault(p=>p.Name == e.Name && p.Age == e.Age);

使用ConstantExpression替换e.Name和e.Age

您可以在上面的方法中看到,我获取所有唯一参数并将它们存储在顶部的parameters中,然后在整个过程中使用相同的变量。这是一个开始,但是我需要替换每个作为params数组传递的Expression<Func<TEntity, bool>>中参数的实例,这就是我失败的地方。

我尝试枚举表达式并使用.Update()方法传递参数

我还尝试了使用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;
    }
}

只是一个快速的想法,你尝试过为第二个参数使用不同的字母吗?(例如p => p.Name,f => f.Age) - Daniel Casserly
感谢输入,但这样做是不可行的,因为您只有一个参数,但却传入了两个。它会抛出“lambda提供的参数数量不正确”的错误。 - Steven Yates
2
为什么不将查询逐个应用,而不是将它们组合起来呢?results = /*Full set*/; foreach(expression) {results = results.Where(expression)} 由于EF使用了IQueryable,因此框架会推迟执行直到需要时,然后将所有谓词组合成一个SQL查询。 - Basic
你是在尝试将多个属性传递给一个方法吗?请查看http://stackoverflow.com/questions/33630945/passing-property-list-as-strongly-typed-parameters/33631401#33631401的更新部分。 - Deepak Sharma
@Basic 感谢您的建议,这比寻找更简单的解决方案更像是一个学习曲线。谢谢。 - Steven Yates
1
@StevenYates 我曾经写过“And”和“Or”谓词组合器(当时我还不太懂L2E)。后来我为了好玩写了一个LinqToElasticsearch提供程序,所以我绝对理解你的想法。只是想确保你有意走了一条漫长的路。 - Basic
1个回答

4

你离正确答案很近了。我不明白你的parameters变量的意义所在,按名称分组是一个错误。为什么不直接从表达式中传递参数?需要时再访问。你的访问者代码没问题。

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

1
我获取了唯一的权限,认为我可以通过替换参数(不在上面的示例中)来重用该实例。你的解决方案有效,非常感谢。现在我知道我哪里错了。 - Steven Yates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接