Nhibernate Many-to-Many Mapping with extra attributes on the relationship
December 5, 2010
I dropped the Fluent from Nhibernate. I just wanted to experience the whole manual XML mapping thing. Well, it turned out not to be a bad experience at all.
Member registration app
I'm writing a member registration application. There are members and these members live on one or more addresses. Some addresses have more members. And on top of that, the relationship itself has some characteristics. Is it a postal address? Is it a temporary address?
This is the classical many to many relationship. How to map that in Nhibernate?
I decided to create two one-to-many relationships and a special relationship class. Here is the class diagram:
As you can see the PersonAddress class has some extra attributes for the relationship: there are two one to many relationships, from Address to PersonAddress and from Person to PersonAddress.
This is the Person class:
As you can see in line 13 I use the Iesi.Collections.Generic.ISet. It maps to an Nhibernate set (which is an IList). There should be an ISet interface in .NET Framework 4.0, but I need to investigate that further. For now, I'm using the ISet.
The Address class has a similar signature:
And then finally there is the PersonAddress class that maps the Person and the Address in a many-to-many relationship:
Object-relational impedance mismatch
Now we will use Nhibernate to solve the object-relational impedance mismatch which in itself is awesomely cool. Because, what is that thing with a RDBMS? Well, a relationship between two tables is defined in one table by adding a foreign key and then the relationship is bidrectional by default. In OOP, it does not work that way. If I add a collection of addresses as a property from a person, the address does not know anything about the persons that may live there. I have to create a collection of persons to the the address class as well (as I did in the above mentioned code). I have to, because I want to know on which addresses a person lives, but I also want to know which persons live on a certain address.
So here is how we map the Person class to the RDBMS:
Remember the ISet Addresses in the Person class? (virtual public Iesi.Collections.Generic.ISet
- inverse=true means, this side is the relationship owner in a bi-directional relationship. If I omitted inverse=true, then Nhibernate would try to execute two different SQL statements.
- cascading: all-delete-orphan - when an object is saved, updated or deleted, check the associations and save/update/delete all the objects found. When an object is removed from the association and not associated with another object (orphaned), then also delete it. When the person object is deleted, the PersonAddress object will be deleted.
- The keycolumn is PersonId, in the PersonAddress object. (It is the foreign key).
- The one-to-many class is the PersonAddress class
This is how I would map the PersonAddress class:
Notice that PersonAddress has a many-to-one relationship with both Person and Address.
Address is, again, similar to Person:
There we go. So, the important things are:
- one-to-many versus many-to-one
- defining the key column
- transitive persistence: know the lifecycle of your objects (set by the cascade attribute).
Creating the databasetables
Nhibernate has the possibility to create tables from the mappings.
First you need to create a database and the Nhibernate creates the tables. And the relationships. Which again is pretty cool. This is how you do that:
You can check the whole thing at Github. Be sure to alter your connectionstring in App.Config.
Maybe you see something that is completely wrong or against good practice. I'd be happy to hear it!
Next time I'll cover Nhibernate, ASP.NET MVC 3 and Dependency Injection.