显式加载 EF 的 DDD 存储库
DDD repositories with EF explicitly loading
我开始涉足领域驱动设计,我在存储库方面遇到了一些问题,事实上 EF Core 显式加载会自动填充我的导航属性。
我有一个存储库,用于加载我的聚合根及其子项。但是,稍后需要加载一些聚合子项(我需要根据日期范围加载这些实体)。
示例:
加载计划所有者
计算日期范围
加载计划所有者的计划
我试图让我的数据访问层与核心层隔离,这是我有一些问题的地方。
想象一下我的存储库中的这个方法:
public List<Schedule> GetSchedules(Guid scheduleOwnePk, DateRange dateRange)
{
var schedules = dbContext.Schedules.Where(x => x.PkScheduleOwner == scheduleOwnerPk && x.StartDate >= dateRange.Start && x.EndDate <= dateRange.End).ToList();
return schedules;
}
我可以通过两种方式从核心层调用这个方法:
//Take advantage of EF core ability to fill the navigational property automatically
scheduleOwnerRepository.GetSchedules(scheduleOwner.Pk, dateRange)
或
var schedules = scheduleOwnerRepository.GetSchedules(scheduleOwner.Pk, dateRange);
//At this moment EF core already loaded the navigational property, so I need to clear it to avoid duplicated results
scheduleOwner.Schedules.Clear();
//Schedules is implemented as an IEnumerable to protect it from being changed outside the aggregator root
scheduleOwner.AddSchedules(schedules);
第一种方法的问题是它将 EF 核心泄漏到核心层,这意味着如果我离开 EF 核心,属性 ScheduleOwner.Schedules
将不再被填充。
第二种方法抽象了 EF 核心,但需要一些额外的步骤来填充 ScheduleOwner.Schedules
。由于 EF 核心会在调用存储库方法后自动加载导航 属性,我不得不在添加结果之前清除它,否则我将插入重复的结果。
遇到这种情况你们是怎么处理的?您是利用 EF 核心功能还是遵循更自然的方法调用存储库方法并使用其结果来填充一些 属性?
感谢您的帮助。
这里有几件事需要考虑。
尽量避免使用域模型进行查询。而是通过 查询层 .
使用 读取模型
聚合是一个完整的单元,就像加载时加载所有内容一样。当您 运行 进入不需要所有相关数据的场景时,这可能表明该数据不是聚合的一部分,但实际上它可能仅在较弱的意义上相关。
例如 Order
到 Customer
。尽管 Order
很可能需要 Customer
,但 Order
本身就是一个聚合。 Customer
可能有一个 OrderIds
的列表,但它可能会很快变大。通常不需要完整的订单列表来确定聚合是否有效或完整。但是,您可能非常需要一个 ActiveOrder
各种值对象的列表,如果需要的话,例如,保持最大订单量,尽管也有多种方法可以处理这种情况。
回到你的场景。 EF 实体不是您的域模型,当我过去不得不使用 EF 时,我会加载实体,然后映射到存储库中的域实体。存储库只会处理域聚合,您应该避免在存储库上使用查询方法。作为最低限度,存储库通常至少有一个 Get(id)
和一个 Save(aggregate)
方法。
我建议使用一个单独的层进行查询,该层 return 的结果尽可能简单。对于像 Count
这样的东西,我可能 return 一个 int
而像 IScheduleQuery.Search(specification)
我可能 return IEnumerable<DataRow>
或者,如果它包含更复杂的数据或者我需要一个阅读模型,我可能 return IEnumerable<Query.Schedule>
.
我开始涉足领域驱动设计,我在存储库方面遇到了一些问题,事实上 EF Core 显式加载会自动填充我的导航属性。
我有一个存储库,用于加载我的聚合根及其子项。但是,稍后需要加载一些聚合子项(我需要根据日期范围加载这些实体)。
示例:
加载计划所有者
计算日期范围
加载计划所有者的计划
我试图让我的数据访问层与核心层隔离,这是我有一些问题的地方。
想象一下我的存储库中的这个方法:
public List<Schedule> GetSchedules(Guid scheduleOwnePk, DateRange dateRange)
{
var schedules = dbContext.Schedules.Where(x => x.PkScheduleOwner == scheduleOwnerPk && x.StartDate >= dateRange.Start && x.EndDate <= dateRange.End).ToList();
return schedules;
}
我可以通过两种方式从核心层调用这个方法:
//Take advantage of EF core ability to fill the navigational property automatically
scheduleOwnerRepository.GetSchedules(scheduleOwner.Pk, dateRange)
或
var schedules = scheduleOwnerRepository.GetSchedules(scheduleOwner.Pk, dateRange);
//At this moment EF core already loaded the navigational property, so I need to clear it to avoid duplicated results
scheduleOwner.Schedules.Clear();
//Schedules is implemented as an IEnumerable to protect it from being changed outside the aggregator root
scheduleOwner.AddSchedules(schedules);
第一种方法的问题是它将 EF 核心泄漏到核心层,这意味着如果我离开 EF 核心,属性 ScheduleOwner.Schedules
将不再被填充。
第二种方法抽象了 EF 核心,但需要一些额外的步骤来填充 ScheduleOwner.Schedules
。由于 EF 核心会在调用存储库方法后自动加载导航 属性,我不得不在添加结果之前清除它,否则我将插入重复的结果。
遇到这种情况你们是怎么处理的?您是利用 EF 核心功能还是遵循更自然的方法调用存储库方法并使用其结果来填充一些 属性?
感谢您的帮助。
这里有几件事需要考虑。
尽量避免使用域模型进行查询。而是通过 查询层 .
使用 读取模型聚合是一个完整的单元,就像加载时加载所有内容一样。当您 运行 进入不需要所有相关数据的场景时,这可能表明该数据不是聚合的一部分,但实际上它可能仅在较弱的意义上相关。
例如 Order
到 Customer
。尽管 Order
很可能需要 Customer
,但 Order
本身就是一个聚合。 Customer
可能有一个 OrderIds
的列表,但它可能会很快变大。通常不需要完整的订单列表来确定聚合是否有效或完整。但是,您可能非常需要一个 ActiveOrder
各种值对象的列表,如果需要的话,例如,保持最大订单量,尽管也有多种方法可以处理这种情况。
回到你的场景。 EF 实体不是您的域模型,当我过去不得不使用 EF 时,我会加载实体,然后映射到存储库中的域实体。存储库只会处理域聚合,您应该避免在存储库上使用查询方法。作为最低限度,存储库通常至少有一个 Get(id)
和一个 Save(aggregate)
方法。
我建议使用一个单独的层进行查询,该层 return 的结果尽可能简单。对于像 Count
这样的东西,我可能 return 一个 int
而像 IScheduleQuery.Search(specification)
我可能 return IEnumerable<DataRow>
或者,如果它包含更复杂的数据或者我需要一个阅读模型,我可能 return IEnumerable<Query.Schedule>
.