.NET ORMs need virtual, and can't deal with sealed?
Asked Answered
R

5

14

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.

Ravi answered 19/3, 2011 at 19:59 Comment(6)
I view code the exact opposite way -- I wish virtual was default (for methods) and I can count the number of times I have sealed a class on my fingers: If you subclass and break something, that is your fault :-) I've run into too much inflexible code (from other libraries) that I can't consume well and can't change.Jewry
I have exactly opposite belief in usage of virtual - use it as much as possible unless you defined something which really have to stay as is. Also I would not believe anybody from MS by defending the decission of not using virtual methods by default. You can simply browse code from different .NET APIs provided by MS including ASP.NET, EF, WCF, WF etc. and you will find that instead of correct design and extensible architecture they're providing limited or no extensibility hidden by sealed, internal, non virtual and static features. Also btw. virtual is often required by mocking frameworks.Moscow
My first rule in programming is don't be purist and use your brain before you write a code. There are always pros and cons of all APIs and Frameworks and your job is to find the best compromise.Moscow
@Ladislav: but... I have a new greenfield project! My inner purist wants to come out and frolic; he's sick of being shoved aside by brownfield concerns ;)Ravi
@Domenic: I cannot agree more. I'm also sick with development I must do in job but simply you should understand that persistance framework provides some magic on behind but they need hooks to be able to execute that magic. That is the compromise.Moscow
@pst: an alternate view to "if you subclass and break something that is your fault" is "if you introduce a security hole in your superclass that a hostile subclasser can use to attack your users, that is the fault of the guy writing the superclass".Mme
S
19

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:

omitting an annotation from my domain objects for the purpose of persistence seems less bad than adding one.

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.

Strum answered 19/3, 2011 at 20:15 Comment(1)
Combined with Mystere Man's "using frameworks always means compromise," your point that persistence is a leaky abstraction was exactly the angle I needed to get my head around these difficulties. The specific points about memory load and performance were great too.Ravi
D
8

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.

Donella answered 19/3, 2011 at 20:23 Comment(3)
This was actually really helpful. I especially like the point about "using frameworks always means compromise," which gives me I think the appropriate direction to look at this from---no framework is magic. And the tip about automapper is great too, although as you say, probably not the best place to spend my time---i.e. it would probably be worthy of Ayende's "stealing from your client" indictment.Ravi
Wish I could accept two answers... just wanted to let you know I was very close to accepting yours :)Ravi
@Ravi - That's ok, Bevan's answer is quite enlightening to me as well ;)Donella
Z
8

There is a reason virtual is not the default for methods in C# [link to interview with Anders Hejlsberg].

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.

often it is expressive to annotate my classes with sealed for all the usual reasons [link to Eric Lippert's blog].

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:

DO NOT seal classes without having a good reason to do so. [paragraph 6.3]

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:

They're More Like Guidelines Anyway

Zumwalt answered 19/3, 2011 at 20:31 Comment(3)
This was a very helpful clarification; I see now that I took away the right lessons from the blog posts I linked to, while misinterpreting their contexts. As you said, really it's about making it easier to make assumptions and thus maintain the code. But the point that unsealing and virtualizing as needed is not so bad makes a lot of sense.Ravi
Well, I disagree with the guidelines here. I think the better guidance is to say that everything should be sealed unless there is a good reason to make it unsealed. Unsealed is a feature. Features have costs. You should be prepared to pay those costs if you decide to implement that feature.Mme
@Eric: Perhaps you should discuss this with Krzysztof and Brad. You clearly are not on the same track here :-). Perhaps the difference here is what Brad describes as "the core difference between a framework and a library." You are writing a library, while frameworks tend to be more open for customization. Let me throw in another qoute from the FDG: "part of designing for extensibility is knowing when to limit it, and sealed types are one of the mechanisms by which to do that." AmenZumwalt
A
1

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.

Aldenalder answered 19/3, 2011 at 21:54 Comment(2)
I am interested in your ideas and would like to subscribe to your newsletter! No seriously, how can I keep myself updated on this?Ravi
@Ravi - Thanks for your interest in the project :). It's a technical report/pilot study for my degree and there's currently a chance it will be published because it's new work. I'll try have more details about this soon - and I'll try to let you know :).Aldenalder
I
0

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:

<class name="MyEntity" table="SomeTable" lazy="false">
</class>
Izy answered 19/3, 2011 at 21:3 Comment(7)
Hmm, OK. My understanding is that this is bad practice, though: ayende.com/Blog/archive/2010/08/04/…Ravi
What I guess I am saying, is that I am wishing for a magic fairy-tale world in which I could get lazy-loading without dynamic proxying. (Perhaps PostSharp-style post-build IL rewriting? What a rabbit hole...)Ravi
Postsharp will actually make your library persistance dependent. In such case you don't need to use POCOs at all and say hello to EntityObject base class in EF.Moscow
@Ladislav - How could using PS make your application persistence dependent? You could easily use an AOP framework, such as PostSharp, to add the virtual keyword to all of your properties in the model.Aldenalder
@Domenic: the code-example in Ayende's example is a bit more drastic. There, al associations are lazy as well. However, specifying 'lazy=true' on the class, has no effect on the lazyness of the associations. I don't think that disabling the dynamic proxies is a bad practice. I've never used dynamic proxies with nhibernate, and I haven't experienced any problems with it so far. (I do use lazy loading on collections / associations though).Izy
@TheCloudlessSky: If you use AOP to add virtual keyword to all your properties then there is no difference to do it immediately in your code, isn't it? If you use AOP to add lazy loading to your navigation properties, you will bind your compiled library to used persistance framework because this lazy loading will need either context from EF or session from NHibernate, etc.Moscow
Sure it's the same compiled code. But it's different because the specific "persistence aspect" is a separate concern, and therefore shouldn't be leaked to the code. The whole point of AOP is to separate these concerns (model and persistence) in the written code and then combine them at run/compile time. Check out my post for more details.Aldenalder

© 2022 - 2024 — McMap. All rights reserved.