【问题标题】:DDD repositories with EF explicitly loading带有 EF 显式加载的 DDD 存储库
【发布时间】:2019-05-16 22:41:50
【问题描述】:

我开始深入研究领域驱动设计,但我在存储库方面遇到了一些问题,而且 EF Core 显式加载会自动填充我的导航属性。

我有一个存储库,用于加载我的聚合根及其子级。但是,一些聚合子项需要稍后加载(我需要根据日期范围加载这些实体)。

例子:

  1. 加载计划所有者

  2. 计算日期范围

  3. 加载计划所有者的计划

我正在尝试将我的数据访问层与核心层隔离,这就是我有一些问题的地方。

想象一下我的存储库中的这种方法:

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 核心功能还是遵循更自然的方法调用存储库方法并使用其结果来填充某些属性?

感谢您的帮助。

【问题讨论】:

  • 我已经有几年没有接触过 EF 了,但是 tt 模板用于为所有数据类生成部分类,因此您可以添加将在模型更新期间持续存在的方法。我曾经使用此功能创建对逻辑层类的隐式强制转换,这些类反映了数据层 (EF) 类。所以我的查询方法将返回没有延迟加载导航属性的逻辑层对象。不确定这是否有帮助。祝你好运!

标签: c# entity-framework domain-driven-design ddd-repositories


【解决方案1】:

这里有几件事需要考虑。

尽量避免使用您的域模型进行查询。而是通过查询层使用读取模型

聚合是一个完整的单元,因为加载时您会加载所有内容。当您遇到不需要所有相关数据的场景时,它可能表明数据不是聚合的一部分,但实际上它可能只是在较弱的意义上相关。

一个例子是OrderCustomer。尽管Order 很可能需要Customer,但Order 本身就是一个聚合。 Customer 可能有一个 OrderIds 列表,但它可能会很快变大。通常不需要完整的订单列表来确定聚合是否有效或完整。但是,您可能需要一个 ActiveOrder 值对象列表,如果需要保持最大订单量,尽管也有多种方法可以处理这种情况。

回到你的场景。 EF 实体不是您的域模型,当我过去不得不使用 EF 时,我会加载实体,然后映射到存储库中的域实体。存储库只会处理域聚合,您应该避免使用存储库上的查询方法。至少,存储库通常至少有一个 Get(id) 和一个 Save(aggregate) 方法。

我建议使用一个单独的层进行查询,该层返回尽可能简单的结果。对于像Count 这样的东西,我可能会返回int,而像IScheduleQuery.Search(specification) 这样的东西我可能会返回IEnumerable&lt;DataRow&gt;,或者,如果它包含更复杂的数据或者我需要读取模型,我可能会返回IEnumerable&lt;Query.Schedule&gt;

【讨论】:

  • 我一直在阅读有关 DDD 的 Julie Lerman 文章,她在其中创建域模型,然后使用 EF 将它们映射到数据库(尽可能使用读取模型)。我一直在遵循这个想法,我在核心层上创建了我的域实体,然后使用 EF fluent 配置将它们映射到数据层上的数据库。据我了解,您是说我应该有存储库和查询层,我应该使用该层从数据库中获取数据?存储库层上的 Get(id) 有什么意义?
  • 存储库应始终使用完整的聚合。存储库使用Get 方法返回一个完整的聚合。 可以查询该聚合,但可能不会针对它进行优化。例如,当您显示Orders 列表时,您不想加载OrderItem 条目,这是存储库为构成有效聚合所做的事情。恕我直言,延迟加载是一种反模式,表明您正在查询域模型。查询层更接近数据,不返回域对象。
  • 所以存储库只会处理领域层,不会与任何持久性相关的东西?我会为此目的使用查询层吗?
  • 存储库用于持久化域对象。查询层参与提供“原始”数据。如果您还没有这样做,也许您可​​能想看看命令/查询职责分离 (CQRS)。
  • 我已经有了一些与 CQRS 相关的东西,但从未将它应用到现实生活中。感谢您的帮助,我将继续阅读 Eric Evans 的书以获取更多关于 DDD 的知识,因为我正在尝试从数据驱动的设计思维方式转变。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-29
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-06
  • 1970-01-01
相关资源
最近更新 更多