在实体框架和 Linq to Entities 中使用规范模式和表达式

Posted

技术标签:

【中文标题】在实体框架和 Linq to Entities 中使用规范模式和表达式【英文标题】:Using Specification Pattern and Expressions in Entity Framework and Linq to Entities 【发布时间】:2017-11-27 09:57:54 【问题描述】:

我在通过实体框架/Linq-to-Entities 访问数据库的存储库中有许多复杂的查询。通常,这些查询是由许多重要的子查询组成的。一般来说,子查询用于不同的存储库方法以及其他域逻辑。将它们放在存储库层外部但可以访问它们是有意义的。

因此,我想使用规范模式来封装其中一些子查询。

我正在为我的规范类使用基类:

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

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

    public virtual bool IsSatisfiedBy(T candidate)
            
        var predicate = ToExpression().Compile();
        return predicate(candidate);
    

    public Specification<T> And(Specification<T> specification)
    
        return new AndSpecification<T>(this, specification);
    

    public Specification<T> Or(Specification<T> specification)
    
        return new OrSpecification<T>(this, specification);
    

示例规范可能如下所示:

 public class IsAssignmentSetForStudentSpecification : Specification<Assignment>

    private readonly Student _student;

    public IsAssignmentSetForStudentSpecification(Student student)
    
        _student = student;
    

    public override Expression<Func<Assignment, bool>> ToExpression()
    
        return x => !x.Exclusions.Contains(_student) &&
                    (
                        _student.Classes.Select(c => c.Subject).Intersect(x.Subjects).Any() ||
                        x.TutorGroups.Contains(_student.TutorGroup) ||
                        x.Houses.Contains(_student.House) ||
                        x.YearGroups.Contains(_student.YearGroup) ||
                        x.Students.Contains(_student)
                    );
    

如您所见,我不希望在每个存储库查询中编写此类代码。

作为存储库查询方法(使用各种规范)可能如下所示:

public ICollection<Assignment> GetAssignmentsDueInForStudent(Student student, DateRange dateRange)

    var isAssignmentAssignedToStudent = new IsAssignmentSetForStudentSpecification(student);
    var isAssignmentDueInDateRange = new IsAssignmentDueInDateRangeSpecification(dateRange);
    var hasStudentCompletedAssignment = new HasStudentCompletedAssignmentSpecification(student);

    return (from a in Set
                    .Where(x => isAssignmentAssignedToStudent
                        .And(isAssignmentDueInDateRange).IsSatisfiedBy(x))
                    .Where(x => !hasStudentCompletedAssignment.IsSatisfiedBy(x))
                select a)
            .ToList(queryOptions);

在上述方法中,Set 是一个IDbSet&lt;&gt;

不幸的是,当我运行查询时,我收到以下错误:

LINQ to Entities 无法识别方法 'Boolean IsSatisfiedBy(Beehive.Domain.Planner.Assignments.Assignment)' 方法,并且该方法无法转换为存储表达式。

我该如何解决这个问题?

【问题讨论】:

您应该使用ToExpression 方法,大概是在查询表达式树之外。例如,Set.Where(isAssignmentAssignedToStudent.And(isAssignmentDueInDateRange).ToExpression()).ToList()。但是表达式也应该是 EF 兼容的,例如 x.Exclusions.Contains(_student)_student.Classes.Select(c =&gt; c.Subject).Intersect(x.Subjects).Any() 很可能不会工作。这是一个艰难的话题。 【参考方案1】:

使用规范模式,您可以将域逻辑移出存储库。你可以有一个非常精简的存储库,看起来像这样:

public ICollection<Assignment> List(ISpecification<Assignment> specification)
    
    return (from a in Set
                    .Where(specification.ToExpression())
                select a)
            .ToList(queryOptions);

然后,您可以在规范类中使用 And Or 和 Not 操作来组合规范,然后再将它们传入。

var isAssignmentDueForStudent = new isAssignmentSetForStudentSpecification(student)
    .And(new IsAssignmentDueInDateRangeSpecification(dateRange))
    .And(new HasStudentCompletedAssignmentSpecification(student).Not());

return assignmentRepository.List(isAssignmentDueForStudent);

这将逻辑排除在您的存储库之外,并且数据层不依赖于您的规范实现。此外,当您实现 AndSpecifications 等时,您将覆盖 ToExpression 来组合表达式,而不是 IsSatisfiedBy,它可能不需要是虚拟的。

【讨论】:

以上是关于在实体框架和 Linq to Entities 中使用规范模式和表达式的主要内容,如果未能解决你的问题,请参考以下文章

在 LINQ to Entities 中批量删除

关于将 Flex 与 WCF 和 Linq to Entities 一起使用的建议

在 LINQ to Entities 查询中无法构造实体或复杂类型

LINQ to Entities 无法识别该方法(在相关实体上)

在 LINQ to Entities 查询中无法构造实体或复杂类型

在LINQ to Entities中不支持指定的类型成员'x'。只支持初始化器实体成员和实体导航属性。