【问题标题】:LINQ expressions can not be translated to Sql when combining specifications in Specification Pattern [duplicate]在规范模式中组合规范时,无法将 LINQ 表达式转换为 Sql [重复]
【发布时间】:2021-10-06 14:25:06
【问题描述】:

我正在使用规范模式来执行数据库过滤,并避免在内存上执行 (I roughly followed this article)。我的基本规范类是这样的:

    public abstract class Specification<T> : ISpecification<T>{

    public abstract Expression<Func<T, bool>> FilterExpr();

    public bool IsSatisfied(T entity)
    {
        Func<T, bool> func = this.FilterExpr().Compile();
        return func(entity);
    }

    public Specification<T> And(Specification<T> otherSpec)
    {
        return new CombinedSpecification<T>(this, otherSpec);
    }
}

从这个基本规范类派生出多个强类型规范,它们各自运行良好。但是,当尝试组合这些规范并在我的 Repository 类中评估 CombinedSpecification 时,就会出现问题:

System.InvalidOperationException:LINQ 表达式 'DbSet() .Where(c => c.ClientCode == __client_0 && c.Status == "Efective")' 无法翻译。以可翻译的形式重写查询,或通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用显式切换到客户端评估。请参阅https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。

下面是CombinedSpecification类:

    internal class CombinedSpecification<T> : Specification<T>
    {
        private Specification<T> leftSpec;
        private Specification<T> rightSpec;

        public CombinedSpecification(Specification<T> aSpec, Specification<T> otherSpec)
        {
            this.leftSpec = aSpec;
            this.rightSpec = otherSpec;
        }

        public override Expression<Func<T, bool>> FilterExpr()
        {
            Expression<Func<T, bool>> firstExpr = this.leftSpec.FilterExpr();
            Expression<Func<T, bool>> secondExpr = this.rightSpec.FilterExpr();

            BinaryExpression combined = Expression.AndAlso(firstExpr.Body, secondExpr.Body);

            return Expression.Lambda<Func<T, bool>>(combined, firstExpr.Parameters.Single());
        }
    }

为了清楚起见,按单一规范进行过滤工作得很好,但是在组合它们时 LinQ 表达式似乎无法翻译(这对我来说似乎很奇怪,因为我没有使用 SQL AFAIK 不支持的任何方法)。我避免显示 Repository 类以减少此问题的数量,但无论如何,这里是我的 Find() 方法的相关行:

    public IEnumerable<TEntity> Find(Specification<TEntity> spec)
        {
            IQueryable<TEntity> result = this.dbSet;

            if (spec != null)
            {
                result = result.Where(spec.FilterExpr());
            }

            return result.ToList();
        }

提前感谢您的帮助,我希望我的第一个问题已经明确说明!

【问题讨论】:

    标签: c# entity-framework linq entity-framework-core specification-pattern


    【解决方案1】:

    问题在于,在您的 lambda 表达式组合实现中,您使用了一个组合两个 lambda 表达式的主体的主体,但只有第一个 lambda 表达式的参数 (firstExpr.Parameters.Single())。

    这样,第二个主体仍然引用了一个 不同 参数(即使它在视觉上可能看起来相同,lambda 表达式中的参数是通过 reference 来标识的,而不是通过名称来标识为您可以在编译时创建它们时考虑)。

    换句话说,您在错误消息中看到的内容

    c => c.ClientCode == __client_0 && c.Status == "Efective"
    

    其实是这样的

    p0 => p0.ClientCode == __client_0 && p1.Status == "Efective"
    

    这当然是无效的,不能翻译(或编译 - 尝试调用Compile(),你会得到运行时异常)。

    您需要确保两个操作数以及生成的 lambda 表达式使用同一个参数实例。

    您仍然可以重用其中一个操作数的参数,但您必须将另一个操作数的主体重新绑定到相同的参数。这通常是通过小型自定义 ExpressionVisitor 来实现的,它会在表达式树中找到所有出现的参数并用另一个参数(或表达式)替换它们 - 很像 string.Replace,但对于表达式:

    
    public static partial class ExpressionExtensions
    {
        public static Expression ReplaceParameter(this Expression target, ParameterExpression parameter, Expression value)
            => new ParameterReplacer { Parameter = parameter, Value = value }.Visit(target);
    
        class ParameterReplacer : ExpressionVisitor
        {
            public ParameterExpression Parameter;
            public Expression Value;
            protected override Expression VisitParameter(ParameterExpression node)
                => node == Parameter ? Value : node;
        }
    }
    

    并用类似的方式修改组合实现

    
    public override Expression<Func<T, bool>> FilterExpr()
    {
        var firstExpr = this.leftSpec.FilterExpr();
        var secondExpr = this.rightSpec.FilterExpr();
    
        var parameter = firstExpr.Parameters[0]; // <--
        var combined = Expression.AndAlso(
            firstExpr.Body, 
            secondExpr.Body.ReplaceParameter(secondExpr.Parameters[0], parameter) // <--
        );
    
        return Expression.Lambda<Func<T, bool>>(combined, parameter); 
    }
    
    

    【讨论】:

    • 谢谢,我一定会对此进行测试并在明天之前给出答案。但是,您介意更深入地了解为什么参数不一样吗?组合规范不应该在同一个实体上评估自己吗?我想说的是,如果我不是在内存上而是在数据库端进行过滤,我不明白为什么第一个表达式的参数与第二个表达式的参数不同。提前致谢!
    • 正如我所说,它甚至在内存中也不起作用,因为当您尝试将表达式编译为Func 时,您会收到错误消息。为什么它们不一样——因为它们来自两个不同的编译时表达式——(A)c =&gt; c.ClientCode == ... 和(B)c =&gt; c .Status == ...。你看? (A) 中的 c =&gt; 与 (B) 中的 c =&gt; 不同。我在答案中提到它们是通过引用而不是名称来标识的。如果我写(A)x =&gt; x.ClientCode == ...和(B)y =&gt; y.Status == ...会更清楚
    • 对不起,我知道您在原始答案中提到了它,只是我在使用 Expression 类型方面缺乏经验,使我难以掌握。谢谢
    猜你喜欢
    • 2014-07-14
    • 2011-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多