具有ORM的富域模型

本文关键字:模型 ORM 具有 | 更新日期: 2023-09-27 18:10:37

我似乎错过了一些东西,大量使用谷歌并没有帮助提高我的理解…
这是我的问题:
我喜欢以不考虑持久性的方式创建域模型,例如:

  1. 如果我不需要virtual,我不想添加它。
  2. 我不喜欢添加默认构造函数,因为我喜欢我的对象总是被完全构造。此外,在依赖注入的上下文中,默认构造函数的需求是有问题的。我不想使用过于复杂的映射,因为我的领域模型使用的接口或其他结构不容易被ORM支持。

对此的一个解决方案是使用单独的域对象和数据实体。使用存储库模式和根据ORM返回的数据实体构建域对象,可以很容易地解决构造域对象的检索问题。使用AutoMapper,这将是微不足道的,并且没有太多的代码开销。

但是这种方法有一个大问题:如果不自己编写代码,我似乎无法真正支持延迟加载。此外,我将有相当多的类为相同的"事情",特别是在WCF和UI的扩展上下文中:

  1. 数据实体(映射到ORM)
  2. 域模型
  3. WCF DTO
所以,我的问题是:我错过了什么?这个问题一般是如何解决的?

更新:到目前为止的答案表明了我所担心的:看起来我有两个选择:

  1. 在域模型上做出妥协,以匹配ORM的先决条件,从而拥有ORM泄漏到
  2. 的域模型创建大量的附加代码

更新:除了公认的答案之外,请参阅我的答案,以了解我如何为自己解决这些问题的具体信息。

具有ORM的富域模型

我怀疑匹配ORM的先决条件一定是"做出妥协"。然而,从高度可靠、松耦合的体系结构的角度来看,其中一些是合理的。

ORM框架的存在只有一个原因;采用您实现的域模型,并将其持久化到类似的DB结构中,而不必实现大量容易出错、几乎不可能进行单元测试的SQL字符串或存储过程。它们也很容易实现诸如延迟加载之类的概念;在需要对象之前的最后一分钟对对象进行补水,而不是自己构建一个大型对象图。

如果您想要存储过程,或者拥有存储过程并且需要使用它们(无论您是否愿意),大多数orm都不是适合这项工作的工具。如果您有一个非常复杂的域结构,以至于ORM无法映射字段和它的数据源之间的关系,那么我会严肃地质疑您为什么要使用那个域和那个数据源。如果您想要100%的POCO对象,而不了解背后的持久性机制,那么您可能最终会绕过ORM的大部分功能,因为如果域没有可以用代理替换的虚拟成员或子集合,那么您将被迫快速加载整个对象图(如果您有一个巨大的相互链接的对象图,这可能是不可能的)。

虽然在领域设计方面,ORM确实需要一些持久性机制领域的知识,但在我看来,ORM仍然会产生更多的SOLID设计。如果没有ORM,您的选择如下:

  • 滚动您自己的存储库,该存储库包含一个方法,用于生成和持久化域中每种类型的"顶级"对象("God object"反模式)
  • 创建每个在不同对象类型上工作的dao。这些类型需要你硬编码ADO datareader和你的对象之间的get和set;在一般情况下,映射极大地简化了过程。dao还必须了解彼此;为了持久化一个Invoice,您需要Invoice的DAO, Invoice也需要InvoiceLine、Customer和GeneralLedger对象的DAO。而且,必须有一个通用的、抽象的事务控制机制内置于所有这些之中。
  • 设置一个ActiveRecord模式,对象持久化自己(并将更多关于持久化机制的知识放入您的领域)
总的来说,第二个选项是最可靠的,但通常情况下,它会变成一个需要维护的三分之二的大问题,特别是在处理包含反向引用和循环引用的域时。例如,为了快速检索和/或遍历,invoiceinedetail记录(可能包含运输票据或税务信息)可以直接引用Invoice以及它所属的InvoiceLine。这将创建一个3节点的循环引用,该引用需要O(n^2)算法来检测对象是否已经被处理,或者需要与反向引用的"级联"行为相关的硬编码逻辑。我之前也曾执行过"图形漫步者";相信我,如果有其他的方法来做这项工作,你不会想这样做的。

所以,总的来说,我的观点是,在一个足够复杂的领域中,orm是所有害处中最小的。它们封装了许多关于持久化机制的非SOLID内容,并将领域关于持久化的知识简化为非常高级的实现细节,这些细节可以分解为简单的规则("所有领域对象必须将其所有公共成员标记为virtual")。

总之,它没有解决

(这里有额外的无用字符来发布我的很棒的答案)

都很好。

我没有答案(但是当我决定添加一些关于存储过程的东西时,评论变得太长了),除了说我的哲学似乎与您的相同,我编写代码或代码生成。

像部分类这样的东西使得这比在。net早期要容易得多。但是orm(作为一种独特的"东西",而不是仅仅从数据库中获取的东西)仍然需要很多妥协,坦率地说,它们对我来说太抽象了。我不喜欢上很多骗人的课,因为我的设计往往有很长的寿命,而且在几年(甚至几十年)的时间里会发生很大的变化。

就数据库端而言,我认为存储进程是必要的。我知道ORM支持它们,但是大多数ORM用户倾向于不这样做,这对我来说是一个巨大的负面影响——因为他们谈论一个最佳实践,然后他们结合到一个基于表的设计,即使它是从代码优先模型创建的。在我看来,如果他们不想以一种利用关系数据库优势的方式使用关系数据库,他们应该考虑对象数据存储。我首先相信代码和数据库-即数据库和对象模型同时来回建模,然后从两端向内工作。我要把它放在这里:

如果你让你的开发人员对你的表编写ORM代码,你的应用程序将无法存活数年。表格需要改变。越来越多的人想要碰到这些实体,现在他们都在使用从表生成的ORM。随着时间的推移,您将需要重构您的表。此外,只有存储过程才能提供任何可用的基于角色的可管理性,而不需要在每个列的GRANT基础上处理每个表——这是非常痛苦的。如果你擅长OO编程,你必须理解受控耦合的好处。这就是存储过程的全部——使用它们,这样您的数据库就有一个定义良好的接口。或者,如果您只想要一个"愚蠢的"数据存储,就不要使用关系数据库。

你看过实体框架4.1代码吗?在IIRC中,域对象是纯poco。

我们在最近的项目中就是这么做的,而且效果很好

  1. 为我们的业务对象使用带有虚拟关键字的EF 4.1,并拥有我们自己的自定义T4模板实现。将ObjectContext封装在一个接口后面,以实现存储库风格的数据访问。
  2. 使用自动转换器在Bo到DTO之间转换
  3. 使用autoMapper在ViewModel和DTO之间进行转换。

您可能会认为视图模型、Dto和业务对象是相同的东西,它们可能看起来相同,但是它们在关注点方面有非常明确的分离。视图模型更多地关注UI屏幕,DTO更多地关注您正在完成的任务,而业务对象主要关注域

在此过程中有一些妥协,但如果你想要EF,那么好处大于你放弃的东西

一年多后,我现在已经为我解决了这些问题。

使用NHibernate,我能够将相当复杂的域模型映射到合理的数据库设计,这不会使DBA畏缩。

有时需要创建一个新的IUserType接口实现,以便NHibernate可以正确地持久化自定义类型。由于nhibernate的可扩展特性,这没什么大不了的。

我发现没有办法避免添加virtual到我的属性而不丢失延迟加载。我仍然不是特别喜欢它,特别是因为代码分析中关于没有派生类覆盖它们的虚拟属性的所有警告,但出于实用主义,我现在可以接受它。

对于默认构造函数,我也找到了一个可以接受的解决方案。我添加了我需要的构造函数作为公共构造函数,我添加了一个过时的受保护的构造函数供NHibernate使用:

[Obsolete("This constructor exists because of NHibernate. Do not use.")]
protected DataExportForeignKey()
{
}