首页 > 解决方案 > 聚合内的聚合列表

问题描述

我正在使用 DDD 聚合对学生和出勤功能进行建模。我最终得到的是具有出勤记录列表的学生班级。我正在使用 EF 核心加载学生以及与学生相关的所有出勤率。

所以每天都会有一个学生的出勤记录。考勤记录可以在一天内多次更新。我最终在 Student 类上创建了一个名为 CreateOrUpdateAttendance(Date date, AttendanceDetails Attendance) 的公共方法,该方法将在内部检查给定日期是否有任何出勤记录,如果没有创建新的出勤实例,则使用提供的出勤率更新此出勤记录详细信息添加/更新与学生相关的出勤列表。因此,每次为学生更新/创建出勤时,都会从数据库中查询学生实体以及所有出勤记录。根据从输入接收到的 Id 查询学生,以检查提供的 Id 是否有效。

问题是随着时间的推移,会有很多与学生相关的出勤记录,这意味着不必要地拉取与学生相关的所有出勤记录,这将导致查询性能不佳。

Student 是这里的正确根吗?不幸的是,EF 核心还不支持过滤子对象。

我的问题是如何处理需要在聚合内更新子实体的用例更具体地说是聚合内实体列表的单个实体?

在这种情况下,我应该将出勤视为不同的聚合吗?这样我就可以独立于学生记录查询出勤记录?

我最终对数据库进行了两次调用,以检查学生(不加载出勤)和另一个以获取出勤记录。

我在这里遗漏了什么?我应该重新考虑我的设计吗?任何指导都会非常有帮助。

标签: sql-serverdesign-patternsdomain-driven-designmicroservicesef-core-2.0

解决方案


Student 是这里的正确根吗?

聚合是一致性边界。它的存在是为了确保其中包含的实体处于与您的业务规则相关的一致(即有效)状态。您尚未描述任何应确保的业务规则或验证,因此我不知道 Student 是正确的聚合。继续阅读...

不幸的是,EF 核心还不支持过滤子对象。

EF Core 不支持过滤子集合,但可能有其他方法可以实现这一点。这是一些伪代码:

var query = 
    from s in ctx.Students
    where s.Id == 1 // Student filter here
    select new 
    {
        Student = s,
        Student = 
            from sa in s.StudentAttendances
            where sa. // Apply filter here
            select sa
    };

// Perform the query against the DB and then return the Student object
// EF will wire up the relationships for you and because you went to the
// DB first before pulling the student, the data is local and you will
// have the full tree
var student = query.ToList().Select(x => x.Student).FirstOrDefault();

在这种情况下,我应该将出勤视为不同的聚合吗?这样我就可以独立于学生记录查询出勤记录?

这取决于您的业务规则。学生的寿命和出勤率是多少?学生可以有任意数量的出勤率吗?多考虑你的写用例,少考虑你的读用例。您需要加载 StudentAttendances 吗?这只是一个读取用例吗?

还要考虑 ONE Student Aggregate 是否足够。当您只想更改电话号码时,是否应该加载所有考勤记录?就像我们有限界上下文一样,您可能会考虑使用限界聚合,例如,在限界上下文中聚合的垂直分区将听起来像一个单一的大概念分成几个较小的概念。

你可以有:

  • 具有过滤出勤列表的学生聚合(如上图所示)
  • StudentAttendance Aggregate(如您所提到的)
  • 一个 StudentWithAttendance 聚合,它独立于 Student 聚合并服务于不同的用例

我在这里遗漏了什么?我应该重新考虑我的设计吗?

这取决于。一般来说,我建议在域级别多考虑,而在数据库级别少考虑。多考虑写而不是读。

EF 在某些方面迫使我们以关系的方式思考,即使我们为我们的域建模,但我们应该抵制这种冲动,并尽可能将 EF 降级为简单的持久性机制,而不是让它驱动我们的域设计决策。


推荐阅读