【问题标题】:DDD, Entity Framework and Entity MappingDDD、实体框架和实体映射
【发布时间】:2017-02-11 09:51:40
【问题描述】:

我目前正在为我们的下一个项目制定架构和模式。我正在考虑使用 DDD,但由于该项目将是中等规模的,因此我试图从代码复制和整体维护的角度使其尽可能简单。

各层如下所示,基本上每一层都是一个单独的组件:

DB -> Domain -> Application -> Web API -> Clients

对于数据库访问,我使用的是 EF Core 1.0。

这是我目前对数据和域层中的类的设计(简化),我对此不太满意。

域:

class Task
{
  private int State { get; set; }
  private string Description { get; set; }
  private int CreatedById { get; set; }            
}

数据(EF):

class TaskData
{
  public int State { get; set; }
  public string Description { get; set; }
  public int CreatedById { get; set; }
  public User CreatedBy { get; set; }
}

理想情况下,我想直接将我的域实体与 ORM 一起使用,但 Task 和 TaskData 并不相同。在任务实体中,我不需要导航属性 CreatedBy,我只需要一个 Id 就可以了,所以我不想用它不关心的东西污染域。

在数据模型中,我将导航属性用于某些报告,因此这种连接在某些情况下很有用。

如你所见,如果我不能直接映射域实体,我必须在数据层做一些映射。更具体地说,在存储库中通过。手动映射器类。因为我的域实体没有公共 getter 和 setter,所以我无法基于属性将 TaskData 映射到 Task 实体。

这让我想到了纪念品模式,所以我创建了一个新类,这似乎只是普通的 DTO:

class TaskSnapshot
{
  public int State { get; set; }
  public string Description { get; set; }
  public int CreatedById { get; set; }
}

我原来的任务实体现在看起来像:

class Task
{
  ...           
  public Task(TaskSnapshot snapshot)
  {
    this.State = snapshot.State;
    this.Description = snapshot.Description;
    this.CreateById = snapshot.CreatedById;
  }

  public TaskSnapshot ToSnapshot()
  {
    return new TaskSnapshot() 
    {
      State = this.State, 
      Description = this.Description, 
      CreatedBy = this.CreatedBy };
  }
}

如您所见,它需要三个具有不同目的但内容非常相似的类来创建和维护。它只是数据和域层。 “重复”也在其他层继续。

现在,当我决定添加一个新字段时,我需要记住添加它的所有位置并正确分配。恐怕这经常会导致错误,因为团队中的某个人只是忘记更新所有代码。

我能做什么:

  1. 在域实体上公开 getter 和 setter,这样我就不需要快照了。 -> 绝对没有!

  2. 向 Task 实体添加不必要的属性(CreatedBy 导航)并直接与 ORM 一起使用。 -> 我宁愿不要。

  3. 将快照类转换为数据模型并将它们与 ORM 一起使用。 -> 我可能不介意那里的导航属性,但这意味着数据模型是域程序集的一部分。 -> 我不知道,我不喜欢它。

有没有建议我如何在不影响域的情况下有效地减少类的数量或将所有分配(映射)集中到一个地方/映射器类?

谢谢。

【问题讨论】:

  • 只是一个问题 - 为什么不先使用代码?这样您就可以直接将数据库映射到您的领域层。
  • 我首先使用代码,但是在我的数据模型中我需要定义导航属性,而我在域实体中不需要。所以这两个模型并不完全相同。
  • 这里有几个策略:vaughnvernon.co/?p=879
  • “在数据模型中,我对某些报表使用了导航属性,因此这种连接在某些情况下很有用”——我认为将报表移动到不同的限界上下文中可能会使事情变得更容易。这意味着您的 Task 实体将与 TaskData 相同。接下来,如果您将 Task 实体属性 getter 设为 public 并将 setter 设为私有,您也可以将 Task 实体用作您的 EF Persistence 实体。看看@thedatafarm.com/data-access/…
  • @TomShane 为什么你不想威胁 ORM 对象作为域对象?它会让你的生活更轻松。此外,为了让您的 ORM/Domain 模型“丰富”,您可以在业务层中使用扩展方法。

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


【解决方案1】:

根据您在问题下方的评论,我要问的第一个问题是:您为什么不想要域模型中的导航属性?如您所见,它使映射复杂化,但有什么具体优势?

还可以查看here,其中声明:“通过关联属性导航是 DDD 中的一个关键概念。”

所以对我来说,听起来你可以通过在你的域模型中允许导航属性来让事情变得非常简单——比如 CreatedBy,为什么它不应该成为你域中的核心概念?当然,您可能希望禁用延迟加载,并且仅在明确请求时才加载导航属性。

【讨论】:

  • 任务域实体永远不会与 CreatedBy 引用交互。它只是实体中这种类型的众多属性之一。我的主要目标是让域模型尽可能干净,只包含它真正需要的东西。我不想在加载实体根目录时加载这些引用(仅在生成报告时)。我不喜欢这种不一致,有时它会为空,有时会在没有实体理解的确切原因的情况下加载。
  • 您不必加载它,正如我所说,它仅在需要时才显式加载。一个为空的引用,为什么会污染你的域?除了“尽可能干净”之外,您还有其他论点/优势吗?我认为您在这里非常纯粹,但这通常会通过尝试解决并非真正问题的事情而导致更多问题和复杂性。不管怎样,让我们​​知道你想要什么解决方案:)
  • 我并不是要狂热于域的清洁度,我已经准备好做出一些妥协,但我不想从一开始就这样做,而不仔细评估其他可能性。另一件事是,我想使用 EF Core 进行持久化,但它目前不能很好地支持值对象。问题是是使用 EF Core 并准备好跨层映射(我想避免),还是使用我没有经验的 NHibernate 之类的东西。那些决定.. :)
  • 好吧,好好想想肯定是好事,最后我相信你会做出正确的决定:)
猜你喜欢
  • 2010-10-09
  • 2011-03-09
  • 1970-01-01
  • 2017-05-21
  • 2014-04-01
  • 2011-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多