Logo

NHibernate

The object-relational mapper for .NET

Get/Load Polymorphism in NHibernate 3

[This article was originally published on my personal blog here. I hereby grant myself permission to re-publish it on NHForge.org.]

[Code for this article is available on GitHub here.]

Nothing gets an OO zealot hot under the collar the way the term polymorphism does. You probably have three questions right now… What does polymorphism have to do with object-relational mapping? How does it relate to NHibernate? And why should I care?

An ORM that supports polymorphic loading allows us to request one type of object, but potentially get an object of a derived type back. As an example, let's say we have the following simple inheritance hierarchy in our application:

Animal Inheritance Hierarchy

We can query for an Animal, but receive back an instance of Dog or Cat instead.

var dog = session.Get<Animal>(dogId);

NHibernate has supported this type of polymorphic loading behaviour for awhile, but the base class (or interface) had to be mapped. If it wasn’t, polymorphic loading would only work when querying with Criteria or LINQ. The following works for both NH 2.1.2 and NH3 regardless of whether the Animal base class is mapped or not.

var animal = session.CreateCriteria<Animal>()
                    .Add(Restrictions.IdEq(dogId))
                    .UniqueResult<Animal>();

// N.B. Use session.Linq<Animal>() in NH2.1.2
var query = from a in session.Query<Animal>()
            where a.Id == dogId
            select a;
var animal = query.Single();

In NHibernate 2.1.2 and earlier, ISession.Get<T>(id) or ISession.Load<T>(id) would fail if T was an unmapped base class or interface. With NHibernate 3, these methods now work regardless of whether T is mapped or not.*

// Works in NH3; works in NH2.1.2 only if Animal is mapped
// In the sample code, works in NH3 for both Animal and UnmappedAnimal base classes
// In NH2.1.2 and before, works for Animal (mapped), but not UnmappedAnimal
var dog = session.Get<Animal>(dogId);
var cat = session.Load<Animal>(catId);

ASIDE: ISession.Get(id) returns null when the entity doesn’t exist in the database, whereas ISession.Load(id) throws an exception. Generally ISession.Load(id) is preferred if you know the entity should exist as NHibernate can return a proxy object that delays hitting the database until the last possible moment. ISession.Get(id) requires querying the database immediately because there is no way to return an object (e.g. a proxy), but later change it to null when accessed.

In NHibernate 3, polymorphic loading works for Criteria, LINQ, and Get/Load. It has not been implemented for HQL. (If you want/need this feature, the NHibernate team is always willing to accept a feature request with patch.) HQL in NH3 supports polymorphic loading if the queried class is imported via <import class=”UnmappedClass”/> in a hbm.xml file.

// Criteria works in NH2.1.2 and NH3
var animal = session.CreateCriteria<UnmappedAnimal>()
                    .Add(Restrictions.IdEq(dogId))
                    .UniqueResult<UnmappedAnimal>());

// LINQ works in NH2.1.2 and NH3 (NH2.1.2 uses session.Linq<T>())
var query = from a in session.Query<UnmappedAnimal>()
            where a.Id == dogId
            select a;
var animal = query.Single();

// Get/Load works in NH3, but fails in NH2.1.2 and earlier
var animal = session.Get<UnmappedAnimal>(dogId);

// HQL works for NH3 if UnmappedAnimal is imported, but fails for NH2.1.2
var animal = session.CreateQuery("from a in AbstractAnimal where a.id = :id")
                    .SetParameter("id", dogId)
                    .UniqueResult<UnmappedAnimal>());

* I should note one restriction on the generic parameter T when calling ISession.Get<T>(id) and ISession.Load<T>(). Polymorphic loading only works if there is a unique persister for T. Otherwise NHibernate throws a HibernateException, “Ambiguous persister for [T] implemented by more than one hierarchy”. What does this mean? Let’s say you have an unmapped abstract base class, such as Entity. (Entity is a class defined in our application, which includes properties common across all persistent entities, such as primary key, audit fields, and similar. It is not required by NHibernate, but often useful for extracting common domain code.) Consider the following contrived example:

Contrived Inheritance Hierarchy

Note that the Animal inheritance hierarchy is mapped and so is Customer. If we try to execute the following code:

var id = 42;
var entity = session.Get<Entity>(id);

We will get a HibernateException as mentioned above. We are asking NHibernate to load an Entity with an id of 42. But primary keys are only unique within a mapped inheritance hierarchy. So there could be a Cat (or Dog) with id of 42 and a Customer with id of 42! So NHibernate fails with a HibernateException since it has no way of returning a list of objects from Get/Load. If you really want to query across inheritance hierarchies, you can do so with Critera or LINQ where you return a list of objects. The following code will work:

var id = 42;
var entities = session.CreateCriteria<Entity>()
                      .Add(Restrictions.IdEq(id))
                      .List<Entity>();

Here’s a NHibernate trick that makes for a good demo, but isn’t terribly practical in real applications… Retrieve a list of all entities in the database:

var allEntities = session.CreateCriteria<object>()
                         .List<object>();

Happy coding!

UPDATE: Fabio Maulo, NH project lead, pointed out to me that HQL in NHibernate 3 can load unmapped classes so long as you make NHibernate aware of the classes via an <import class=”UnmappedAnimal”/> directive in a hbm.xml file. Thanks, Fabio.


Posted Wed, 16 February 2011 12:46:00 PM by James Kovacs
Filed under: querying

comments powered by Disqus
© NHibernate Community 2024