我开始涉足域驱动设计,并且我的存储库存在一些问题,并且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 core会在调用存储库方法后自动加载导航属性,因此我被迫在添加结果之前将其清除,否则我将插入重复的结果。
你们如何处理这种情况?您是利用EF核心功能还是遵循更自然的调用存储库方法并使用其结果填充某些属性的方法?
感谢您的帮助。
答案 0 :(得分:3)
这里有几件事要考虑。
尝试避免使用域模型进行查询。而是通过查询层使用读取模型。
聚合是一个完整的单元,因此在加载时加载所有内容。当您遇到不需要所有相关数据的情况时,它可能表明该数据不是聚合的一部分,但实际上可能只是在较弱的意义上相关。
一个示例是Order
至Customer
。尽管Order
可能非常需要Customer
,但是Order
本身就是一个集合。 Customer
可能具有OrderIds
的列表,但是列表可能很快变得很大。通常,不需要完整的订单清单即可确定汇总有效或完整。但是,如果为了保持最大订购量而需要这样做,则很可能需要一个ActiveOrder
类排序的值对象列表,尽管也有多种方法可以处理这种情况。
回到您的情况。 EF实体不是您的域模型,过去我不得不使用EF时,我将加载该实体,然后映射到存储库中的域实体。该存储库将仅处理域聚合,并且您应避免在该存储库上使用查询方法。至少,存储库通常至少应具有Get(id)
和Save(aggregate)
方法。
我建议使用单独的图层进行查询,该图层返回的结果应尽可能简单。对于类似Count
的东西,我可能会返回int
,而类似类似IScheduleQuery.Search(specification)
的东西,我可能会返回IEnumerable<DataRow>
,或者,如果它包含更复杂的数据或者我需要读取模型我可能会返回IEnumerable<Query.Schedule>
。