Monday, March 24, 2008

NHibernate - Working Around Lack of Component Inheritance

Lets say we want to map this class hierarchy:

image

Table wise we only have one table called MultipleClassesToOneTable:

image

So we're trying to map MainClass and MainClassKind to a single table and we want it to handle the fact that MainClassKind has subclasses. Why are we doing this, because we're working with a pre-existing database that is hard to work with.

Ideally we want MainClassKind to behave like a <component> but we can't map it that way because <component> does not support inheritance so the question is how do we map it?

MainClass

This is the class that would act as our aggregate root and which will manage the ID, its mapping is quite simple:

<class name="MainClass"
table="MultipleClassesToOneTable" lazy="false">

<
id name="Id" column="Id">
<
generator class="identity" />
</
id>

<
property name="Name" column="Name" />

<
one-to-one name="Kind" access="property" cascade="all-delete-orphan" />
</
class>

Note that this class is mapped to MultipleClassesToOneTable and its generating the identity value which is stored in Id. The class also has an association to a MainClassKind, but its a pretty dull ordinary mapping file.

MainClassKind

If we could map this class as a component of MainClass then we would, since we can't we have to do something a bit more interesting:

<class name="MainClassKind" table="MultpleClassesToOneTable" lazy="false">

<
id name="Id" column="Id" >
<
generator class="foreign">
<
param name="property">MainClass</param>
</
generator>
</
id>

<
discriminator column="Kind" insert="false" />
<
property name="Kind" access="property"/>

<
property name="Description" column="Description" />

<
one-to-one constrained="true" name="MainClass" access="property"/>

<
subclass discriminator-value="0" name="FirstKind" >
<
property name="FirstClassesExtraValue" access="property"/>
</
subclass>

<
subclass discriminator-value="1" name="SecondKind"/>

</
class>

So what is notable about this mapping:

  1. Generator - The Id is coming from the associated MainClass object.
  2. Discriminator Mapped Twice - The Kind column is mapped twice, once as the discriminator and once as a property. We only have the property mapping because for some reason when we use <discriminator> like this it doesn't update the associated column, I believe this is a bug. I've thus set insert="false" on the discriminator and mapped the property.
  3. Shared Row - As discussed we need to have a relationship back to the MainClass to allow us to use its Id. We could map this as a field but here I've included it as a property. Anyway it's mapped as a <one-to-one> and since both map to the same row they do technically have the same ID.

Hrm...

Result

Well I'm not exactly happy with this mapping, it seems like a hacky way of getting the behaviour I want, but I guess its an option if you really want to redesign your model in such a way without redesigning your database. Is there a better way to handle this though?

Share This - Digg It Save to del.icio.us Stumble It! Kick It DZone

4 comments:

  1. Nice way of mapping two classes and one table. We tried to use this example for our State pattern, but NHibernate does not like us to change the state of the object and than save it: We get an NHibernate exception that the new (State) object cannot be changed.

    To clarify an example: We have one table (Dog) with a State property of type DogState. DogState is an abstract class with the implementatations DogWild and DogCalm.

    When "Dog.Bark()" is called, we delegate the bark to the current state of the dog:
    Dog.DogState.Bark()
    Depending if DogState is Wild or Calm, the Dog will yield or just do nothing.
    The State is mapped as a simple String in the DogTable: we could not save our Dog after changing the state. Did you find a way to do this with the above mapping strategy?

    ReplyDelete
  2. @Hace
    Yup I've had that exact issue, I'd either convert the string into the appropriate state in the domain e.g:

    protected DogState State
    {
    get
    {
    return DogStateFactory.Get(_mappedString);
    }
    }

    Obviously you wouldn't do it exactly like that but you get the idea. Other solution is a user type, the NHibernate forum had a discussion on that a while back:

    http://groups.google.com/group/nhusers

    ReplyDelete
  3. Hi Colin,

    Yes, that is exactly what we did as well. We have a protected string property "MappedStated" used for persisting the state in the database. However we let NHibernate control setting the state for when the object is loaded from the database again by having the "MappedState" setter as a protected string, so that NHibernate can access it but collegues (users of the class) can not...

    I'll look into user types as well. Keep updating your blog with your NHibernate findings!

    ReplyDelete
  4. This worked well as I was in a similar situation. Thanks for taking the time to post this gem!

    ReplyDelete