【问题标题】:EF Core incapsulation in Unit Of Work pattern工作单元模式中的 EF Core 封装
【发布时间】:2026-01-13 15:45:01
【问题描述】:

我在加入 DDD 和 EF Core 时遇到问题。

我正在使用 DDD 架构制作项目。作为数据访问级别,我使用来自here 的通用工作单元模式。

public interface IUnitOfWork
{
    IRepository<TDomain> Repository<TDomain>() where TDomain : class;
}

public interface IRepository<TDomain>
{
    TDomain Get(Expression<Func<TDomain, bool>> predicate);
}

我使用 EF Core 实现这些接口。

我有一些包含 2 个类的域模型

public class MainClass
{
    public int Id { get; set; }

    public List<RelatedItem> Items { get; set; }
}

public class RelatedItem
{
    public int Id { get; set; }

    public MainClass Parent { get; set; }

    public DateTime Date { get; set; }

    public string SomeProperty { get; set; }
}

在我的项目MainClass 的现实生活中收集了数百个RelatedItems。为了执行某些操作,每个请求我只需要一个RelatedItem 和某个日期。可以通过Items属性搜索来完成。

在工作单元中封装 EF Core 的性能我必须从数据库中显式加载带有相关项目的实体,因为业务登录层对 UnitOfWork 存储库的实现一无所知。但是这个操作很慢。

所以我决定创建MainClassService,它注入costructor unitOfWork 并有一个只返回一个RelatedItem 的方法,它工作正常。

public class MainClassService
{
    IUnitOfWork unitOfWork;

    public MainClassService(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork ?? throw new ArgumentNullException();
    }

    public RelatedItem GetRelatedItemByDate(int mainClassId, DateTime date)
    {
        return unitOfWork.Repository<RelatedItem>().Get(c => c.Parent.Id == mainClassId && c.Date == date);
    }
}

因此,由于 EF Core,我无法直接使用属性 Items,但由于 DDD 架构,我应该使用它们。

而我的问题是:使用这样的结构可以吗?

【问题讨论】:

  • 您的要求有点不清楚。您是在问是否可以将 unitOfWork 注入 MainClassService 还是在问您应该遵循哪个概念模型(EF Core 与 DDD 方法)?如果是后者,在 DDD 中,您的域对象应该是不了解持久性的。不要让 EF 决定您如何与域对象交互。相反,让 EF(或其他 DAL 代码,如您的存储库)适应您的 DDD 架构。
  • 什么是让您创建这两个领域模型类的业务规则?
  • @bman7716 你说得对,我对后者感兴趣。
  • @Constantin Galbenu 这只是一个例子。一个包含大量相关实体的聚合
  • @Timothy 相关还是嵌套?

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


【解决方案1】:

从您的问题看来,MainClass 是一个聚合根,RelatedItem 是一个嵌套实体。此设计决策应基于必须保护的业务规则/不变量。当 Aggregate 需要变异时,必须从存储库中完全加载,即 Aggregate 根及其所有嵌套实体和值对象在执行变异命令之前必须在内存中,无论它有多大。

此外,将基础设施服务注入聚合(或嵌套实体)不是一个好习惯。如果您需要这样做,那么您必须重新考虑您的架构。

因此,从我所写的内容中,您可以看到问题仅在您尝试改变聚合时才会出现。如果您只需要阅读或查找它,您可以创建一些专用服务来使用基础架构组件查找数据。您的MainClassService 似乎就是这种情况,您只需要阅读/查找一些RelatedItem 实体。

为了明确目的只是读取,MainClassService 需要返回RelatedItem 实体的只读表示。

所以,您刚刚迈出了迈向 CQRS 的第一步,其中模型分为两个:READ 模型和 WRITE 模型。

【讨论】:

  • @Timothy 请更具体一些。命令只是一个普通的对象,它不能调用任何东西。
  • 对不起,我是这方面的新手。可以在 CommandHandler 调用查询中执行方法吗?这与 CQRS 方法不矛盾吗?
  • @Timothy 您可以调用查询服务,但不会破坏领域驱动设计。此外,您需要考虑到这些查询服务返回过时的数据(最终一致的数据)。因此,您允许调用查询服务,例如在进行查找时:假设您有命令ActivateUser(userId) 但您只有userEmail,您可以在之前通过电子邮件查找用户的 ID执行命令。无论如何,细节很大程度上取决于您的编程语言/使用的框架/等。
  • “不破坏 DDD”是什么意思?
  • @Timothy DDD(领域驱动设计)带有一些关于聚合的“规则”。其中之一是每个事务不修改多个聚合。我一般指的是尽量不在命令处理程序中对长时间运行的业务流程进行建模,因为服务器可能会重新启动并使您的系统处于不一致的状态。