.NET ORM 需要虚拟,并且无法处理密封?
我刚刚开始使用 .NET ORM,甚至还没有在 Entity Framework 和 NHibernate 之间做出决定。但在这两种情况下,我都遇到了一个问题,因为他们似乎希望我以各种方式损害域模型的完整性,特别是在 C# 对象设计的更精细的方面。这是有关该主题的几个问题之一。
virtual
不是 C# 中方法的默认值是有原因的。我的领域模型中的对象不准备对子类的行为做出承诺,除非在非常特殊的情况下我将它们标记为此类。换句话说,对于我的域对象上的极少数方法,为未指定的新功能添加挂钩是否合适。
然而 NHibernate 希望我将所有内容都虚拟化,而实体框架希望我将所有实体引用虚拟化。我意识到他们为什么需要它(创建代理对象),并且我意识到这实际上是继承和虚拟的合法使用——它们实际上正在连接到我的属性为了添加新功能。但令我恼火的是,我必须用完全与持久性有关的东西来注释我的域模型类,而根本不表达它们与实现者和消费者的实际契约。
作为一个较小的问题,我意识到我可能无能为力,通常用 sealed
出于所有常见原因。不过,这并没有那么令人讨厌,因为为了持久性而从我的域对象中省略注释似乎比添加注释要好。
令人沮丧的是,在阅读了《Effective C#》等书籍或 Eric Lippert 等博客(其中就如何设计富有表现力且防弹的 C# 对象提供了很好的建议)多年之后,使用 ORM 的需求却让我感到沮丧很多知识都被抛在了窗外。我希望这里有人能够指出我错在哪里,无论是在我对他们能力的理解上,还是在我对领域建模和 ORM 的作用的思考上。
I am just getting started with .NET ORMs, to the point where I haven't even decided between Entity Framework and NHibernate. But in both cases, I'm running into a problem in that they seem to want me to compromise the integrity of my domain model in various ways, especially on finer points of C# object design. This is one of several questions on the subject.
There is a reason virtual
is not the default for methods in C#. The objects in my domain model are not prepared to make promises about the behaviors of subclasses, except in very specific cases where I mark them as such. Put another way, for very few methods on my domain objects is it appropriate to add a hook for unspecified new functionality.
Yet NHibernate wants me to make everything virtual
, and Entity Framework wants me to make all entity references virtual
. I realize why they need it (to create proxy objects), and I realize it's actually a legitimate use of inheritance and virtual
---they actually are hooking in to my properties in order to add new functionality. But it grates on me that I have to annotate my domain model classes with something that is entirely about persistence, and not at all expressive of their actual contract to implementers and consumers.
As a smaller issue, which I realize I probably cannot do anything about, often it is expressive to annotate my classes with sealed
for all the usual reasons. This is a bit less grating though, since omitting an annotation from my domain objects for the purpose of persistence seems less bad than adding one.
It is frustrating that after several years reading books like Effective C# or blogs like those of Eric Lippert, which give great advice on how to design expressive and bulletproof C# objects, the need to use ORMs is making me throw much of that knowledge out of the window. I am hoping that someone here can point out where I am wrong, either in my grasp of their capabilities or in my thinking about domain modeling and the role of ORMs.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
不仅仅是 .NET ORM——同样的限制也适用于 Java ORM。
不过,在 Java 中,除非您明确声明,否则一切都是虚拟的,因此满足 ORM 的需求与您在
sealed
中发现的情况非常相似:归根结底是这样的:持久性无知是一个值得实现的目标,但它不是一个可以 100% 实现的目标,除非您也愿意忽略内存负载和性能等次要细节也是如此。
如果不关心内存负载和性能,请停止使用代理并要求所有对象在水合后立即完全填充 - NHibernate 可以通过配置来做到这一点。副作用是所有相关对象都将一次性加载,因此最终会将大部分数据库加载到内存中。该应用程序需要大量内存并且需要花费大量时间来启动 - 但它会起作用。
持久性是一种泄漏抽象 - 虽然您可以将大部分内容隐藏在幕后,但始终是泄漏到应用程序其他区域的元素。
It's not just .NET ORMs - the same constraints apply to Java ORMs as well.
Though, in Java, everything is virtual unless you explicitly declare otherwise, so the need to satisfy the ORM is much like the situation you're finding with
sealed
:What it boils down to is this: Persistence Ignorance is a worthwhile goal, but it's not one that can be 100% achieved unless you're willing to also ignore minor details like memory load and performance as well.
If memory load and performance are of no concern, stop using proxies and require all your objects to be fully populated as soon as they're hydrated - NHibernate can do this through config. The side effect will be that all related objects will be loaded in one go, so you'll end up with most of the database loaded into memory. The app will need a lot of memory and take a lot of time to start up - but it'll work.
Persistance is a leaky abstraction - while you can hide most of it behind the curtain, there will always be elements that leak into other areas of your application.
如何温和地表达...抱歉,我不能。克服它。
我100%同意你的观点,但使用框架总是意味着妥协。不想妥协?自己构建。这就是它的全部内容。
为了不那么对立,您的问题有一个解决方案,那就是使用像自动映射器这样的东西在泄漏的持久性子系统和应用程序的其余部分之间进行转换。基本上,您可以保持领域模型干净整洁,并按照您喜欢的方式进行设计,然后使用转换层在它和令人讨厌、丑陋的 ORM 之间进行映射。
但是,这确实有很多工作要做。您放弃少量的纯度就可以节省大量的精力。
How to put this gently.... Sorry, I can't. Get over it.
I agree with you 100%, but using frameworks always means compromise. Don't want to compromise? Build it yourself. That's really all there is to it.
To be a little less antagonistic, There is a solution to your problem, and that's to use something like automapper to translate between your leaky persistence subystem and the rest of your application. Basically, you keep your domain model clean and tidy and designed exactly the way you like, and then use a translation layer to map between it and your nasty, ugly ORM.
But, that's really a lot of work. And for the small amount of purity you give up you save a lot of effort.
Hejlsberg实际上是在谈论框架API设计。他没有提及任何有关业务线应用程序的内容。因此,他的规则在 LOB 应用中应用较少。由于您使用的是 O/RM,因此您可能正在编写 LOB 应用程序。
您引用的是 Eric Lippert 的一篇文章,他在 C# 编译器团队的工作背景下撰写了该文章。一般的框架设计指南实际上包含相反的指南:
换句话说,Eric Lippert 所说的不是通用规则。
就我个人而言,当我编写 LOB 应用程序时,我实际上会密封我的类并尽可能编写非虚拟方法。然而,这与在以后的版本中引入重大更改无关,因为这几乎完全是一个框架设计问题。
不,我这样做是因为它让我更容易对我的代码做出假设。换句话说:它使我的代码更易于维护。
然而,当我需要这样做时,我绝对没有问题解封一个类或虚拟化一个方法。我这样做的主要原因是为了让我的代码可以测试。
显然您也需要这种灵活性,并且由于您正在编写 LOB 应用程序,因此请务实并记住:
Hejlsberg is actually talking about framework API design. He doesn't say anything about Line of Business applications. Therefore, his rules apply less in LOB applications. Since you're using an O/RM, you're probably writing a LOB appliation.
You are referencing one of Eric Lippert's articles, who wrote that article in the context of his work at the C# compiler team. The general Framework Design Guidelines actually contain an opposite guideline:
In other words, what Eric Lippert is saying is not the common rule.
Personally, when I am writing LOB applications, I actually seal my classes and write non-virtual methods whenever possible. However, this has nothing to do with the change of introducing breaking changes in a later release, because this is almost solely a framework design problem.
No, I do this because it makes it easier for me to make assumptions about my code. In other words: it makes my code more maintainable.
However, I have absolutely no problem what so ever unsealing a class or virtualizing a method when I need to do this. The main reason for me to do so is to allow my code to be testable.
Apparently you need this flexibility too and since you are writing a LOB application, just be practical and remember that:
我理解这种挫败感。一种可能性是使用面向方面的编程 (AOP) 框架(例如 PostSharp)在编译时将所有属性标记为“虚拟”。这样做的缺点是 PS 编织过程所涉及的开销,这会增加总体编译时间。
只是为了好玩:我现在实际上正在从事一个研究项目,它是一个基于 AOP 的初步 ORM(暂时称为 Trinity)。它的目标是拥有延迟加载的全部能力,而无需引入代理(或
virtual
关键字)。最重要的是,它允许模型独立于持久化(不涉及继承、POCO 对象等),但提供与 NHibernate 等相同的功能。AOP 仍然处于研究水平,但它是一个有趣的项目。一旦论文准备好,我将尝试开源该项目。
I understand the frustration. One possibility is using an aspect-oriented programming (AOP) framework such as PostSharp to mark all properties
virtual
at compile-time. The downside to this is the overhead involved with PS's weaving process which increases overall compile time.Just for fun: I'm actually working on a research project right now that is a preliminary AOP-based ORM (tentatively called Trinity). It's goal is to have the full capacity for lazy-loading without needing to introduce proxies (or the
virtual
keyword). Most importantly, it allows the models to persistence independent (no inheritance involved, POCO objects etc), but providing the same capabilities as something like NHibernate.AOP's still very much at the research level, but it's been an interesting project to work on. I'll be trying to open source the project once the paper is ready.
没有必要在 NHibernate 中将所有东西都虚拟化。
如果您不使用“动态代理”,那么您不必将所有内容都虚拟化,并且可以使您的类密封。
NHibernate 默认使用动态代理。通过这样做,NHibernate 创建一个从您的类继承的类,以便它可以确保在检索实例时,仅填充该类的标识符。仅当您第一次需要访问其中一个属性时,才会加载该类的属性。
您可以通过在类映射上指定
lazy=false
来禁用动态代理功能:It is not necessary to make everything
virtual
in NHibernate.If you do not make use of 'dynamic proxies', then you do not have to make everything virtual, and you can make your classes sealed.
NHibernate uses dynamic proxies by default. By doing so, NHibernate creates a class that inherits from your class, so that it can make sure that, when an instance is retrieved, only the identifier of that class is populated. The properties of the class will only be loaded when you first need to access one of the properties.
You can disable the dynamic - proxy functionality, by specifying
lazy=false
on your class-mapping: