Mapping A State Pattern With NHibernate
In the comments of my previous post – Descriptive State Enumeration – Maxim Tihobrazov asked me to show how to map a state pattern with NHibernate; and I am more than happy to oblige!
NHibernate Mapping Options
I certainly don’t claim to be an expert on NHibernate, but I do use it on a daily basis and I’ve solved my fair share of problems – including how to map a State pattern with NHibernate. According the NHibernate inheritance documentation, there are three core ways of mapping this pattern:
- Table per class hierarchy (I’ve always called it “record per class”) – each subclass (state instance, in this case) is associated with a specific record in a table. All of the subclasses are found in the same table.
- Table per subclass (also called a “joined sub class”) – each sub class has it’s own table and is joined to the super class by a one to one relationship with it’s primary key.
- Table per concrete class – each sub class has it’s own table and is mapped by the specific class type, not by the abstraction.
My Choice – Table Per Class Hierarchy
In my current set of applications, I’ve always used the table per class hierarchy option so that is what I will describe here. My reasons for choosing this option are simplicity in the mapping and more importantly – not having a need to store the records in their own tables. Each of my states can be very cleanly represented as a record in my state table.
Setting Up Shop
Let’s consider the same coffee shop that I’ve used in both of my PTOM posts – in this case, we’re dealing specifically with the Order class and it’s OrderStatus state. It’s necessary to note that when I am working with NHibernate and the state pattern, I actually do set up my state models a little different. First off – we need an Id for NHibernate to map to the table’s primary key. Secondly, I like to keep the core information in the abstract base class and provide all the varying values via a constructor. Third, each of the inheriting classes needs to provide a default (no args) constructor. Forth, we need a way for NHibernate to know which class it should actually instantiate, when loading – a “descriminator”. I like to use a simple string Name property for this. And lastly – I think (though I am likely wrong on this aspect) that the abstract base class needs to provide a default constructor. Fortunately, the constructors we need don’t need to be public – they can be private.
With all of that being said, here is realistic code base that I would use for my Order and OrderStatus model:
The Fluent NHibernate Maps
I’m a huge fan of Fluent NHibernate. I’ve been using it since just after it was branched from the original ShadeTree code. And at one point, I had submitted so many patches that I was made a comitter on the project. So, it should be no surprise to anyone that I’m going to advocate using FluentNHibernate over the .hbm.xml mapping files. Truth be told, I don’t even remember how to do the needed descriminators in hbm.xml files. There was some trick to the descriminator being the first specified items after the Id or something… it’s just easier to do in FluentNHibernate, so I don’t bother with hbm.xml files anymore.
The Order map is going to be as standard as any other map. You don’t need to do anything special here. Just map the Id of the Order and the referenced OrderStatus. (I like to add a “CreateMap()” method that is called from the constructor, so that I can avoid the “virtual method call from a constructor” warning. But I also use the “treat warnings as errors” option for my C# projects).
It’s really the OrderStatus map that is special in this case. This is where we get into the details of the descriminator – telling NHibernate which specific instance to create, when loading the data from the database. In our case, we have added a “Name” field to our OrderStatus object and we will be explicitly using this property as the descriminator.
The key to all of this is the DescriminateSubClassesOnColumn method. The generics
Then, we have the specific class instances referenced by the “.Subclass
And finally, we have to provide an ugly workaround for our maps via the “MapSubclassColumns” method. There is an implementation issue in FluentNHibernate currently, and because of this issue we are forced to call the “MapSubclassColumns” method, with an empty lambda expression. If we don’t call this method, the sub class will not get registered and NHibernate will not know how to handle the data in question. (I am hoping to fix this issue at some time, and make it so we don’t have to call that method. I just haven’t had time recently.)
Wrapping Up The Map
The rest of the map (the one remaining “name” property being mapped, in this case) is a regular NHibernate map. You’ll notice, though, that we never mapped the “DispalyForFullfillment” property. I explicitly choose to leave any and all “volatile” properties out of my state maps. By doing this, I am able to add, edit, and remove any properties or methods on the state objects that I need, without having to change the NHibernate mappings. Since the state objects I am dealing with don’t change without recompiling the code anyway, I don’t need the ability to define them in the database. However, if you do need or want the flexibility of defining your states in the database, you can map each individual property of the state. Just remember that you will have to modify both the code and the database, to make the changes complete.
I hope this quick look at mapping a state pattern with Fluent NHibernate will help to shed some light on the subject, for someone. I realize that I have only provided the FluentNHibernate mapping, though. I specifically chose to do this because it would be a serious chore for myself to create a code project with NHibernate set up so that I can actually verify my mapping xml is correct. However, the translation from FluentNHibernate back to standard .hbm.xml files should be fairly straightforward, with the help of the NHibernate Documentation. If anyone out there is willing to help out and send me the correct .hbm.xml mapping, I would be more than happy to add it to this post and credit you with the work.