NHibernate Gotchas - Living With Legacy Databases
As we've used NHibernate more and more we've begun to hit against issues, particularly when working against a "legacy" database. So if you are working against a green fields database then your probably fine, you can design from the domain down but if you're not then I think two issues in particular are worth bearing in mind:
- Inability to map inheritance at the component level.
- Inability to map unidirectional many-one in a satisfactory way in some cases.
Before I continue I should say two things. The first is that the class names are unimportant, I made them up as I don't want to use real class names or examples from our project. Secondly I am not going to provide solutions, I don't know if it’s even possible for an ORM solution to handle these issues better than NHibernate does but you should still know about them.
Components And Inheritance
NHibernate component mappings are great and can improve your domain model but there are limits.
Take the example of an Customer table that has 50+ columns, don't ask me why but it does. You want to redesign the table but thats something to be planned and executed carefully, DB refactoring is painful especially when you have multiple legacy systems and reports/DTS's working against the database.
So for now you want to design your domain model to have a core Customer class and then lots of little inheritance hierarchies off it. For example if the Customer came directly from your Web site then maybe 5 fields in the database are used and if they came indirectly its a different 10.
In each of the two cases you have different behavior and certainly different validation requirements so you decide to have an ICustomerSource interface (making this up as I go) with two subclasses called DirectCustomerSource and ExternalCustomerSource. The Customer will then have a reference to an ICustomerSource which it will be passed in it constructor.
The problem is you cannot do this because your mapping would have each subclass of ICustomerSource mapped as a component, meaning that when the Customer is saved so should the ICustomerSource. The problem is that the component mapping does not support mapping inheritance so, as far as I know, you are slightly stuffed.
You could certainly subclass Customer and get around it that way but that doesn't always work and certainly wouldn't work for us because we actually want to have multiple little class hierarchies hanging off Customer.
I've logged this "issue" in the NHibernate JIRA.
Unidirectional Many-One Associations
Let’s say we have Order and OrderItems. The Order is the root of an aggregate containing OrderItems. In this case we might actually want to do this in the domain by having the Order have a collection of OrderItems. Maybe not for this situation but we often do.In the domain we want to make the association unidirectional though, we can go from a Order to an OrderItem but we don't want to do the reverse association.
Our database design has the ID of the Order as a foreign key in the OrderItem table. Many people see this as a valid database design and so it’s quite possible this is the way you have it done.
The problem is this isn't going to work unless we do one of two things:
- Make the foreign key in the OrderItem table nullable.
- Add the association from OrderItem to Order.
This topic is discussed in depth in the forums:
- Describes why Hibernate works that way.
- Another post with more context.
- Yet another post on it.
Of course you can just use one of the work arounds but its worth knowing that this issue exists as it can catch you out. I've logged an issue in the NHibernate Jira about it.
Summary
Neither issue should convince you that NHibernate is a dead loss, it is still magic but it does have limits and you need to be aware of them.
Got to agree, NHibernate does have short comings... but the benefits far outweigh the negatives.
ReplyDeleteAnother of those shortcomings is the "weak" support for generics.
1) Although NHib offers support for Nullable CLS structs, it does not support custom nullable structs
2) It does not support Custom generic entities (although I believe it does support custom collections)
Colin,
ReplyDeleteThe second issue is a result of putting the Order class in charge of filling the OrderId column in the OrderLines tables.
Since it has to do it in a different statement than the one that persists the OrderLine itself, the OrderId must be nullable.
Why not just make the OrderLine responsible for it?
I don't understand your first issue (plus jira link is broken), so I won't say anything about that.
ReplyDeleteThe second one is entirely a problem with NHibernate's implementation/design tradeoffs. (I use a custom ORM that handles the scenario correctly, i.e. will cause an order and its orderitems to be saved within the same transaction and not require a nullable foreign key).
Ahh OK. The first one (component inheritance).
ReplyDeleteOften when you map a component you actually don't want a single class but a little inheritance hierarchy. So an Account might have an InvestmentAim class hanging off it and mapped as a component (so coming from the Account table).
However further domain analysis (v2) shows that there are multiple kinds of InvestmentAim so you want to create a little inheritance hierarchy, without changing the database.
In that situation it'd be nice to be able to create another column in the Account table and then specify that it is the discriminator for the InvestmentAim hierarchy.
Does that make more sense?
Ok, I understand now. The short answer is that its not entirely NHibernate's fault - its a design tradeoff. The long answer:
ReplyDeleteThe basic mechanics of class inheritance with discriminators anywhere in the hierarchy is easy for an ORM to support. The implications are more difficult though, because they have to balance inheritance against the concept of object identity - which requires that each row in the database has to be uniquely identifiable as belonging to one and only one class. My impression is that NHibernate treats object identity as non-negotiable, and within that context, what you ask for is impossible (in .NET or Java).
Inheritance is not always the best solution anyway. Specifically, we have to beware of using inheritance when there is some chance that the object instance may want to switch roles. (Most languages, cannot support switching types).
In the ORM that I use, discriminators can be used for either inheritance (as described by you) or "roles" (which allow switching). The inheritance compromise is that object identity is not strictly enforced (it does exist though).
I don't think NHibernate has the concept of roles, which is a pity, since it is very useful, and allows some of what you would like to achieve without the complications of inheritance.