带有ORM的丰富域模型

时间:2011-10-07 17:27:00

标签: c# .net architecture

我似乎遗漏了一些东西,谷歌的广泛使用无助于提高我的理解......
这是我的问题:
我喜欢以持久性无知的方式创建我的域模型,例如:

  1. 如果我不需要,我不想添加virtual
  2. 我不想添加默认构造函数,因为我喜欢我的对象总是完全构造。此外,在依赖注入的上下文中,对默认构造函数的需求是有问题的。
  3. 我不想使用过于复杂的映射,因为我的域模型使用了ORM不易支持的接口或其他构造。
  4. 对此的一个解决方案是拥有单独的域对象和数据实体。使用存储库模式可以轻松地检索构造的域对象,并从ORM返回的数据实体构建域对象。使用AutoMapper,这将是微不足道的,而不是太多的代码开销。

    但是我对这种方法有一个大问题:似乎我不能真正支持延迟加载而不自己编写代码。另外,对于相同的“东西”,我会有很多类,特别是在WCF和UI的扩展上下文中:

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

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

      1. 在域模型上做出妥协以匹配ORM的先决条件,从而拥有ORM泄漏到的域模型
      2. 创建大量附加代码
      3. 更新:
        除了接受的答案之外,请参阅my answer以获取有关我如何为我解决这些问题的具体信息。

6 个答案:

答案 0 :(得分:5)

简而言之 - 它没有解决

(这里还有其他无用的字符可以发布我真棒的答案)

答案 1 :(得分:5)

我认为匹配ORM的先决条件必然是“妥协”。然而,从高度SOLID,松散耦合的架构的角度来看,其中一些是公平的。

ORM框架存在的唯一原因;采用您实现的域模型,并将其保存到类似的数据库结构中,而不必实现大量容易出错,几乎不可能的单元测试SQL字符串或存储过程。他们还可以轻松实现延迟加载等概念;在需要该对象之前的最后一分钟给对象加水,而不是自己构建一个大的对象图。

如果您想要存储过程,或者需要它们并且需要使用它们(无论您是否想要),大多数ORM都不是正确的工具。如果您有一个非常复杂的域结构,以至于ORM无法映射字段与其数据源之间的关系,我会严重质疑您使用该域和该数据源的原因。如果您想要100%POCO对象,而不了解后面的持久性机制,那么您可能最终会围绕ORM的大部分功能进行最终运行,因为如果域没有虚拟成员或子集合可以用代理替换,然后你就不得不急于加载整个对象图(如果你有一个庞大的相互关联的对象图,这可能是不可能的)。

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

  • 滚动您自己的存储库,其中包含一个方法,用于生成和保留域中的每种类型的“顶级”对象(“上帝对象”反模式)
  • 创建每个处理不同对象类型的DAO。这些类型要求您对ADO DataReader和对象之间的get和set进行硬编码;在一般情况下,映射大大简化了过程。 DAO也必须彼此了解;要保留发票,您需要发票的DAO,它还需要为InvoiceLine,Customer和GeneralLedger对象提供DAO。并且,必须在所有这些中内置一个通用的,抽象的事务控制机制。
  • 设置ActiveRecord模式,其中对象可以自行保存(并将更多有关持久性机制的知识放入您的域中)

总的来说,第二种选择是最强的SOLID,但更多时候它会变成野兽和三分之二来维护,特别是在处理包含反向引用和循环引用的域时。例如,对于快速检索和/或遍历,InvoiceLineDetail记录(可能包含运输说明或税务信息)可能直接引用Invoice以及它所属的InvoiceLine。这创建了一个3节点循环引用,要求O(n ^ 2)算法检测已经处理了对象,或者需要关于反向引用的“级联”行为的硬编码逻辑。我以前必须实施“图形步行者”;相信我,如果有其他方式可以做这项工作,你不想这样做。

因此,总而言之,我的观点是,鉴于域名足够复杂,ORM是所有邪恶中最少的。它们封装了很多关于持久性机制的非SOLID,并将关于其持久性的域的知识减少到分解为简单规则的非常高级的实现细节(“所有域对象必须将所有公共成员标记为虚拟”)。

答案 2 :(得分:3)

所有优点。

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

像部分类这样的东西比以前的.NET时代要容易得多。但是ORM(作为一种独特的“事物”,而不是只是在进出数据库时所做的事情)仍然需要很多妥协,而且坦率地说,它们对我来说太抽象了。我并不是很重要,因为我的设计往往有很长的寿命,而且这些年来经历了很多变化(几十年甚至几十年)。

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

如果您允许开发人员针对您的桌面编写ORM代码,那么您的应用将会遇到多年无法生存的问题。表需要改变。越来越多的人想要敲击这些实体,现在他们都在使用从表生成的ORM。而且你会想要随着时间的推移重构你的表。此外,只有存储过程才能为您提供任何类型的可用的基于角色的可管理性,而无需处理每列GRANT基础上的每个tabl - 这是非常痛苦的。如果您在OO中编程良好,则必须了解受控耦合的好处。这就是所有存储过程 - 使用它们,因此您的数据库具有明确定义的接口。如果您只是想要一个“哑”数据存储区,请不要使用关系数据库。

答案 3 :(得分:0)

您是否先查看过Entity Framework 4.1 Code? IIRC,域名对象是纯粹的POCO。

答案 4 :(得分:0)

这就是我们在最新项目中所做的,而且效果非常好

  1. 将EF 4.1与虚拟关键字一起用于我们的业务对象,并拥有我们自己的T4模板自定义实现。将ObjectContext包装在存储库样式dataaccess的接口后面。
  2. 使用automapper在Bo To DTO之间进行转换
  3. 使用autoMapper在ViewModel和DTO之间进行转换。
  4. 你会认为viewmodel和Dto和Business对象是相同的东西,它们可能看起来相同,但它们在关注方面有一个非常明确的分离。 视图模型更多地是关于UI屏幕,DTO更多地是关于您正在完成的任务,而Business对象主要关注域

    一路上有一些混淆,但如果你想要EF,那么你的好处就会超过你所放弃的东西

答案 5 :(得分:0)

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

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

有时需要创建IUserType接口的新实现,以便NHibernate可以正确地保留自定义类型。感谢NHibernates的可扩展性,这没什么大不了的。

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

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

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