【问题标题】:.NET ORMs need virtual, and can't deal with sealed?.NET ORM 需要虚拟,不能处理密封?
【发布时间】:2011-07-18 21:13:18
【问题描述】:

我刚刚开始使用 .NET ORM,甚至还没有在 Entity Framework 和 NHibernate 之间做出决定。但是在这两种情况下,我都遇到了一个问题,他们似乎希望我以各种方式损害我的域模型的完整性,尤其是在 C# 对象设计的更精细点上。这是有关该主题的几个问题之一。


有a reason virtual is not the default for methods in C#。我的领域模型中的对象不准备对子类的行为做出承诺,除非在非常特殊的情况下我将它们标记为此类。换句话说,对于我的域对象上的极少数方法,为未指定的新功能添加挂钩是合适的。

然而 NHibernate 想要我制作所有东西 virtual,而 Entity Framework 想要我制作所有实体引用 virtual。我意识到他们为什么需要它(创建代理对象),并且我意识到这实际上是对继承的合法使用,virtual---他们实际上正在连接到我的属性以添加新的功能。但令我感到恼火的是,我必须用完全与持久性有关的东西来注释我的域模型类,而不是表达它们与实现者和消费者的实际合同。

作为一个较小的问题,我意识到我可能无能为力,通常用 sealed for all the usual reasons 注释我的类很有表现力。不过,这有点不那么令人讨厌,因为为了持久性而从我的域对象中省略注释似乎没有添加注释那么糟糕。


令人沮丧的是,在阅读了诸如 Effective C# 之类的书籍或 Eric Lippert 之类的博客(它们为如何设计富有表现力和防弹的 C# 对象提供了很好的建议)之后,使用 ORM 的必要性是令人沮丧的。让我把很多知识扔出窗外。我希望这里的人能指出我错在哪里,无论是在我对他们能力的掌握中,还是在我对领域建模和 ORM 角色的思考中。

【问题讨论】:

  • 我以完全相反的方式查看代码——我希望 virtual 是默认值(用于方法),我可以计算我手指上有 sealed 类的次数:如果您继承并破坏某些东西,那是您的错 :-) 我遇到了太多不灵活的代码(来自其他库),我无法很好地使用并且无法更改。
  • 我对虚拟的使用有着完全相反的信念——尽可能多地使用它,除非你定义了一些必须保持原样的东西。此外,我不会相信任何来自 MS 的人为默认不使用虚拟方法的决定辩护。您可以简单地浏览来自 MS 提供的不同 .NET API 的代码,包括 ASP.NET、EF、WCF、WF 等。您会发现,它们提供的不是正确的设计和可扩展架构,而是由密封的内部隐藏的有限或没有可扩展性,非虚拟和静态特征。顺便说一句。虚拟框架通常需要模拟框架。
  • @Ladislav:但是...我有一个新的未开发项目!我内心的纯粹主义者想出来嬉戏;他厌倦了被棕地问题推到一边;)
  • @Domenic:我完全同意。我也厌倦了我必须在工作中做的开发,但你应该明白,持久性框架在背后提供了一些魔力,但它们需要钩子才能执行这种魔力。这就是妥协。
  • @pst:“如果你继承并破坏了你的错”的另一种观点是“如果你在你的超类中引入了一个安全漏洞,一个敌对的子类可以用来攻击你的用户,那是编写超类的人的错”。

标签: .net nhibernate entity-framework orm class-design


【解决方案1】:

我理解这种沮丧。一种可能性是使用 PostSharp 等面向方面的编程 (AOP) 框架在编译时标记所有属性 virtual。这样做的缺点是与 PS 的编织过程有关的开销,这会增加整体编译时间。

只是为了好玩:我现在实际上正在研究一个基于 AOP 的初步 ORM(暂时称为 Trinity)的研究项目。它的目标是在不需要引入代理(或virtual 关键字)的情况下拥有延迟加载的全部能力。最重要的是,它允许模型独立于持久性(不涉及继承、POCO 对象等),但提供与 NHibernate 之类的功能相同的功能。

AOP 仍处于研究阶段,但它是一个有趣的项目。论文准备好后,我将尝试开源该项目。

【讨论】:

  • 我对您的想法很感兴趣,并愿意订阅您的时事通讯!不认真,我怎样才能让自己了解这一点?
  • @Domenic - 感谢您对项目的兴趣 :)。这是我的学位的技术报告/试点研究,目前有机会发表,因为它是新工作。我会尽快提供有关此的更多详细信息 - 我会尽量让您知道:)。
【解决方案2】:

虚拟不是 C# 中方法的默认值 [链接到 采访 Anders Hejlsberg]。

Hejlsberg 实际上是在谈论框架 API 设计。他没有提及业务线应用程序。因此,他的规则在 LOB 应用程序中的应用较少。由于您使用的是 O/RM,因此您可能正在编写 LOB 应用程序。

通常注释我的 密封的所有通常的类 原因 [链接到 Eric Lippert 的博客]。

您正在引用 Eric Lippert 的一篇文章,他在 C# 编译器团队的工作背景下撰写了那篇文章。一般的Framework Design Guidelines实际上包含了相反的准则:

请勿在没有证书的情况下密封类 这样做的充分理由。 [第 6.3 段]

换句话说,Eric Lippert 所说的不是通用规则。

就个人而言,当我编写 LOB 应用程序时,我实际上会密封我的类并尽可能编写非虚拟方法。但是,这与在以后的版本中引入破坏性更改的更改无关,因为这几乎完全是一个框架设计问题。

不,我这样做是因为它使我更容易对我的代码做出假设。换句话说:它使我的代码更易于维护。

但是,当我需要这样做时,我完全没有问题解封一个类或虚拟化一个方法。我这样做的主要原因是让我的代码可测试。

显然,您也需要这种灵活性,并且由于您正在编写 LOB 应用程序,因此请切合实际并记住:

无论如何,它们更像指南

【讨论】:

  • 这是一个非常有用的说明;我现在看到我从链接到的博客文章中吸取了正确的教训,同时误解了它们的上下文。正如您所说,这实际上是为了更容易做出假设并因此维护代码。但是根据需要解封和虚拟化并没有那么糟糕这一点很有意义。
  • 好吧,我不同意这里的指导方针。我认为更好的指导是说一切都应该被密封,除非有充分的理由让它开封​​。 未密封是一项功能。功能是有成本的。如果您决定实施该功能,您应该准备好支付这些费用。
  • @Eric:也许你应该与 Krzysztof 和 Brad 讨论这个问题。您显然不在同一条轨道上:-)。也许这里的区别就是 Brad 所描述的“框架和库之间的核心区别”。您正在编写一个库,而框架往往对定制更加开放。让我引用 FDG 的另一句话:“为可扩展性设计的一部分是知道何时限制它,而密封类型是实现这一目标的机制之一。”阿门
【解决方案3】:

没有必要在 NHibernate 中创建所有 virtual。 如果您不使用“动态代理”,那么您不必将所有内容都虚拟化,并且可以将您的类密封。

NHibernate 默认使用动态代理。通过这样做,NHibernate 创建了一个继承自您的类的类,因此它可以确保在检索实例时,仅填充该类的标识符。仅当您第一次需要访问其中一个属性时才会加载该类的属性。

您可以通过在类映射中指定lazy=false 来禁用动态代理功能:

<class name="MyEntity" table="SomeTable" lazy="false">
</class>

【讨论】:

  • 嗯,好的。我的理解是,这是一种不好的做法:ayende.com/Blog/archive/2010/08/04/…
  • 我想我想说的是,我希望有一个神奇的童话世界,在其中我可以在没有动态代理的情况下进行延迟加载。 (也许是 PostSharp 风格的后期构建 IL 重写?真是个兔子洞……)
  • Postsharp 实际上会使您的库持久性依赖。在这种情况下,您根本不需要使用 POCO 并向 EF 中的EntityObject 基类问好。
  • @Ladislav - 使用 PS 如何使您的应用程序持久性依赖?您可以轻松地使用 AOP 框架(例如 PostSharp)将 virtual 关键字添加到模型中的所有属性中。
  • 当然是相同的编译代码。但它有所不同,因为特定的“持久性方面”是一个单独的问题,因此不应该泄露给代码。 AOP 的重点是在编写的代码中分离这些关注点(模型和持久性),然后在运行/编译时将它们组合起来。查看我的帖子了解更多详情。
【解决方案4】:

如何轻轻地放这个......对不起,我不能。克服它。

我 100% 同意你的观点,但使用框架总是意味着妥协。不想妥协?自己建造。这就是它的全部内容。

为了减少对立性,有一个解决方案可以解决您的问题,那就是使用 automapper 之类的东西在您的泄漏持久性子系统和应用程序的其余部分之间进行转换。基本上,你保持你的领域模型干净整洁,并按照你喜欢的方式设计,然后使用翻译层在它和你讨厌的、丑陋的 ORM 之间进行映射。

但是,这确实是很多工作。而对于你放弃的少量纯度,你会节省很多精力。

【讨论】:

  • 这真的很有帮助。我特别喜欢关于“使用框架总是意味着妥协”的观点,这给了我一个合适的方向来看待这个问题——没有框架是魔法。关于 automapper 的提示也很棒,尽管正如您所说,可能不是消磨时间的最佳场所——即这可能对得起 Ayende 的“从你的客户那里窃取”的指控。
  • 希望我能接受两个答案...只是想让你知道我非常接近接受你的答案:)
  • @Domenic - 没关系,Bevan 的回答对我也很有启发 ;)
【解决方案5】:

不仅仅是 .NET ORM - 同样的约束也适用于 Java ORM。

尽管在 Java 中,除非您明确声明,否则一切都是虚拟的,因此满足 ORM 的需求很像您在 sealed 中发现的情况:

为了持久性而从我的域对象中省略注释似乎没有添加注释那么糟糕。

归结为:持久性无知是一个有价值的目标,但它不是可以 100% 实现的,除非您也愿意忽略诸如 内存负载 和 性能。

如果 内存负载 和 性能 无关紧要,请停止使用代理并要求所有对象在水合后立即完全填充 - NHibernate 可以做到这通过配置。副作用是所有相关对象都将一次性加载,因此您最终会将大部分数据库加载到内存中。该应用程序需要大量内存并需要很长时间才能启动 - 但它会工作。

持久性是一个leaky abstraction - 虽然您可以将其大部分隐藏在幕后,但总会有一些元素泄漏到您应用程序的其他区域。

【讨论】:

  • 结合 Mystere Man 的“使用框架总是意味着妥协”,你关于持久性是一个有漏洞的抽象的观点正是我需要理解这些困难的角度。关于内存负载和性能的具体点也很棒。
猜你喜欢
  • 1970-01-01
  • 2011-06-04
  • 1970-01-01
  • 2015-02-14
  • 1970-01-01
  • 1970-01-01
  • 2016-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多