Ayende @ Rahien

It's a girl

NHibernate: Complex relationships

Originally posted at 11/18/2010

I got an interesting question today (I am teaching my NHibernate course now).

The tabular structure is similar to this:

image

But the desired object structure is:

image

That is quite different than the tabular model, but it is actually very easy to handle this with NHibernate.

Here are the mapping for the Address entity. We use the <join/> tag to have an entity that spans more than a single table:

<class name="Address"
       table="Addresses">
  <id name="Id">
    <generator class="identity"/>
  </id>
  <property name="City" />

  <join table="PeopleAddresses" >
    <key column="AddressId"/>
    <property name="IsDefault"/>
    <property name="ValidFrom"/>
    <property name="ValidTo"/>
  </join>

</class>

We then map the Person, using standard many-to-many mapping for the addresses:

 <class name="Person"
             table="People">

   <id name="Id">
     <generator class="identity"/>
   </id>
   <property name="Name" />

   <bag name="Addresses" table="PeopleAddresses" inverse="true">
     <key column="PersonId"/>
     <many-to-many class="Address" column="AddressId"/>
   </bag>
   
 </class>

There is just one thing thing to be aware of, you can’t add new addresses via the Person.Addresses collection, because the PeopleAddresses table has more data in it than just the keys. Presumably, you are handling this in some other fashion already.

All in all, this is a pretty elegant solution.

Comments

Victor Kornov
11/19/2010 12:25 PM by
Victor Kornov

Pardon my ignorance, but how "you can’t add new addresses via the Person.Addresses collection" relates to the conclusion of "this is a pretty elegant solution"? All I can think of is having another set of classes\mappings just to be able to save the person's address. That's not elegant in my books.

Ayende Rahien
11/19/2010 12:45 PM by
Ayende Rahien

Victor,

You need to map the PersonId column on the PeopleAddresses, and then you can threat this as a standard Many To One.

The scenario that was brought up was were there was another way of handling that, so I didn't bother with that

JasonW
11/19/2010 12:51 PM by
JasonW

The tabular model and the object structure in almost all cases will be quite different, with one modelling data and the other modelling behaviour. This is something that most ORMs don't handle as elegantly as the code above.

Max
11/19/2010 01:04 PM by
Max

What about using <idbag?

Max
11/19/2010 01:07 PM by
Max

Oops, it seems my comment is stripped.

What about using idbag?

Corey
11/19/2010 02:23 PM by
Corey

If you really want flexibility given the table structure above and still want to handle inserts.

Create a sql view that fits the exact mapping structure and let the view deal with the inserts appropriately.

Fabio Maulo
11/19/2010 04:21 PM by
Fabio Maulo

Can I refactorize that DB ? or it is untouchable ?

Scooletz
11/20/2010 08:51 AM by
Scooletz

Good, old-fashioned blog entry. Interesting case.

Frans Bouma
11/20/2010 11:08 AM by
Frans Bouma

I'm with Victor, this isn't elegant at all. It's also wrong: PersonAddresses is an objectified relationship (in Object Role Modeling / NIAM terms), and this means it's an entity by itself. Just because it defines a m:n relationship between person and address doesn't make it an entity which doesn't exist, and the limitation that you can't persist new addresses shows that.

I fail to see why PersonAddresses has to be wiped under the rug: its name is perhaps badly chosen, but it is a valid entity, that you don't want to program with it, is really ignorance: because the entity exists in the domain, you have to deal with it, and which is the reason you have to deal with it too only in 'some other fashion'.

The m:n relationship between person and addresses is readonly, over the objectified relationship. I do recall having a discussion with Fabio about this some time ago on nhusers.

Nestor Rodriguez
11/22/2010 03:07 PM by
Nestor Rodriguez

This challenge is an evidence of how NHibernate can deal with bad databases design but refactor is a must !.

Comments have been closed on this topic.