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.

Domenic
  • 110,262
  • 41
  • 219
  • 271
  • 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. –  Mar 19 '11 at 20:38
  • 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. – Ladislav Mrnka Mar 19 '11 at 21:10
  • 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. – Ladislav Mrnka Mar 19 '11 at 21:17
  • 2
    @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 ;) – Domenic Mar 19 '11 at 21:19
  • 1
    @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. – Ladislav Mrnka Mar 19 '11 at 21:22
  • 2
    @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". – Eric Lippert Mar 20 '11 at 14:44

5 Answers5

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.

Bevan
  • 43,618
  • 10
  • 81
  • 133
  • 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. – Domenic Mar 19 '11 at 21:18
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.

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
  • 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. – Domenic Mar 19 '11 at 21:12
  • Wish I could accept two answers... just wanted to let you know I was very close to accepting yours :) – Domenic Mar 19 '11 at 21:17
  • @Domenic - That's ok, Bevan's answer is quite enlightening to me as well ;) – Erik Funkenbusch Mar 19 '11 at 21:21
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

Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
Steven
  • 166,672
  • 24
  • 332
  • 435
  • 2
    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. – Domenic Mar 19 '11 at 21:14
  • 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. – Eric Lippert Mar 20 '11 at 14:42
  • @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." Amen – Steven Mar 20 '11 at 21:48
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.

TheCloudlessSky
  • 18,608
  • 15
  • 75
  • 116
  • I am interested in your ideas and would like to subscribe to your newsletter! No seriously, how can I keep myself updated on this? – Domenic Mar 20 '11 at 00:36
  • @Domenic - 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 :). – TheCloudlessSky Mar 20 '11 at 01:35
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>
Frederik Gheysels
  • 56,135
  • 11
  • 101
  • 154
  • Hmm, OK. My understanding is that this is bad practice, though: http://ayende.com/Blog/archive/2010/08/04/nhibernate-is-lazy-just-live-with-it.aspx – Domenic Mar 19 '11 at 21:06
  • 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...) – Domenic Mar 19 '11 at 21:08
  • 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. – Ladislav Mrnka Mar 19 '11 at 21:16
  • @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. – TheCloudlessSky Mar 19 '11 at 21:42
  • @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). – Frederik Gheysels Mar 19 '11 at 21:59
  • @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. – Ladislav Mrnka Mar 19 '11 at 22:01
  • 2
    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. – TheCloudlessSky Mar 19 '11 at 23:05