1

我正在使用规范模式来执行数据库过滤,并避免在内存上进行(我大致遵循了这篇文章)。我的基本规范类是这样的:

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

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

4

1 回答 1

1

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

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

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

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

于 2021-10-06T18:13:03.663 回答