Ayende @ Rahien

It's a girl

NHibernate – Mapping a single domain model to multiple physical data models

A while ago I sat down and talked with a colleague about the Entity Framework, he raved about how important the separation of the logical model from the physical one is. I don’t really buy into that, but that is  beside the point.

Last week, on the Progressive.NET NHibernate workshops, I setup, quite accidently, to create a single domain model and map it to several physical data models. I promised to share the code, and I think that this form is as good as any.

Let us start from the following domain model:

image

I am going to present three things for each physical data model manifestation. The mapping, the database schema and the result of the following query:

s.CreateQuery("from Owner owner where owner.Name = 'ayende'")
    .List<Owner>();

We will start with the classic, table per class, which looks like this:

<class name="Company">
    <id column="Id"  type="System.Int32">
        <generator class="hilo"/>
    </id>
    <property name="Name"/>
    <property name="CompanyRegistrationId"/>
    <set name="Horses" table="CompanyHorses">
        <key column="Company"/>
        <many-to-many class="Horse" column="Horse"/>
    </set>
</class>

<class name="Individual">
    <id column="Id"  type="System.Int32">
        <generator class="hilo"/>
    </id>
    <property name="Name"/>
    <property name="Email"/>
    <set name="Horses" table="HorsesBelongToIndividuals">
        <key column="Company"/>
        <many-to-many class="Horse" column="Horse"/>
    </set>
</class>

<class name="Consortium">
    <id column="Id"  type="System.Int32">
        <generator class="hilo"/>
    </id>
    <property name="Name"/>
    <set name="Horses" table="HorsesBelongToIndividuals">
        <key column="Company"/>
        <many-to-many class="Horse" column="Horse"/>
    </set>
    <set name="Owners" table="ConsortiumOwners">
        <key column="Consortium"/>
        <many-to-any id-type="System.Int32" meta-type="System.String">
            <meta-value class="Individual" value="Individual"/>
            <meta-value class="Company" value="Company"/>
            <meta-value class="Consortium" value="Consortium"/>
            <column name="OwnerType"/>
            <column name="OwnerId"/>
        </many-to-any>
    </set>
</class>

The database schema is:

image

You might want to pay some attention to the association between Consortium and its owner, using <many-to-any/>.

Trying to execute the aforementioned query will give us an error, NHibernate is not aware of any persistent class called Owner and HQL queries are not polymorphic over unknown types. Criteria API, however, are. And we can execute the following query successfully:

s.CreateCriteria<Owner>()
    .Add(Restrictions.Eq("Name", "ayende"))
    .List<Owner>();

Which result in:

image

image

image

Let us look at another scenario, which would keep the same data model, but let NHibernate know about the inheritance association between the classes. NHibernate call this type of association union subclasses. Here are the mapping:

<class name="Owner" abstract="true">
    <id column="Id"  type="System.Int32">
        <generator class="hilo"/>
    </id>
    <set name="Horses" table="OwnerHorses">
        <key column="Owner"/>
        <many-to-many class="Horse" column="Horse"/>
    </set>
    <property name="Name"/>
    <union-subclass name="Company">
        <property name="CompanyRegistrationId"/>
    </union-subclass>

    <union-subclass  name="Individual">
        <property name="Email"/>
    </union-subclass>

    <union-subclass name="Consortium">
        <set name="Owners" table="ConsortiumOwners">
            <key column="Consortium"/>
            <many-to-many class="Owner" column="Owner"/>
        </set>
    </union-subclass>
</class>

You might notice how simple the mapping looks like. For that matter, look at how we could simplify the mapping between horses and owners.

image

The resulting query is… interesting:

image

And now let us move to the more classic inheritance schemes, first, let us look at table per hierarchy:

<class name="Owner" abstract="true">
    <id column="Id"  type="System.Int32">
        <generator class="hilo"/>
    </id>
    <discriminator column="Type" type="System.String"/>
    <set name="Horses" table="OwnerHorses">
        <key column="Owner"/>
        <many-to-many class="Horse" column="Horse"/>
    </set>
    <property name="Name"/>
    <subclass discriminator-value="Company" name="Company">
        <property name="CompanyRegistrationId"/>
    </subclass>

    <subclass discriminator-value="Individual" name="Individual">
        <property name="Email"/>
    </subclass>

    <subclass discriminator-value="Consortium" name="Consortium">
        <set name="Owners" table="ConsortiumOwners">
            <key column="Consortium"/>
            <many-to-many class="Owner" column="Owner"/>
        </set>
    </subclass>
</class>

With the following schema:

image

In this case, Owner.Type is the discriminator for the Owner hierarchy. And the query that is generated is:

image

And finally, we have table per subclass, which we use the following mapping:

<class name="Owner" abstract="true">
    <id column="Id"  type="System.Int32">
        <generator class="hilo"/>
    </id>
    <set name="Horses" table="OwnerHorses">
        <key column="Owner"/>
        <many-to-many class="Horse" column="Horse"/>
    </set>
    <property name="Name"/>
    <joined-subclass name="Company">
        <key column="Id"/>
        <property name="CompanyRegistrationId"/>
    </joined-subclass>

    <joined-subclass name="Individual">
        <key column="Id"/>
        <property name="Email"/>
    </joined-subclass>

    <joined-subclass name="Consortium">
        <key column="Id"/>
        <set name="Owners" table="ConsortiumOwners">
            <key column="Consortium"/>
            <many-to-many class="Owner" column="Owner"/>
        </set>
    </joined-subclass>
</class>

This gives us this schema:

image

Using this approach, we query them using:

image

I would like to point out that this is just a sample of the type of things that we can do with NHibernate, and that I took the easy path doing so. There are actually more complex inheritance option available (discriminator with joined classes, for example) and it is pretty easy to change things even further.

And, of course, it is entirely possible to take things in the other direction, and have a single physical data model that can map to several domain models.

Have fun…

Comments

Peter Morris
05/19/2009 10:00 AM by
Peter Morris

Discriminator with joined classes sounds like an interesting (hint) example ;-)

Colin Jack
05/19/2009 12:15 PM by
Colin Jack

(discriminator with joined classes, for example)

Yeah in general the variety of mappings you've shown are a very poweful aspect of NHibernate, and I liked the discriminator with joined classes approach when we used it too. Good stuff.

Jason
05/19/2009 01:28 PM by
Jason

This is a great post about mapping options. Thank you for a post on (what i consider) advanced features of NH.

Omer Mor
05/19/2009 07:54 PM by
Omer Mor

On a side note:

You're known as a guy who hate magic strings (wasn't that what made you write RhinoMocks?).

So why don't you use Linq to NHibernate in your samples?

Ayende Rahien
05/19/2009 09:17 PM by
Ayende Rahien

Omer,

Because L2NH isn't able to express all the richness that I have here.

Ayende Rahien
05/19/2009 09:34 PM by
Ayende Rahien

Omer,

I love the idea, but I am currently teaching, and I want to teach people from the basic up

Omer Mor
05/19/2009 09:36 PM by
Omer Mor

Oh - sorry. I guess I was completely out of context here.

Michel Grootjans
05/20/2009 08:47 PM by
Michel Grootjans

Thanks for the transcription of your session Oren. I couldn't quite figure out how to reproduce your work at SkillsMatter.

Ayende Rahien
05/23/2009 11:05 AM by
Ayende Rahien

Peter, just try implementing that, it isn't hard

Comments have been closed on this topic.