CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Peter's Gekko

public Blog MyNotepad : Imho { }

nHibernate many-to-many collections. (OR mapping is not one table one class)

Mapping collections in nHibernate is at first sight quite confusing. There are loads and loads of possibilities. The official documentation is somewhat academic. This is good because, once you have got it, it is a clear reference. But it is bad because the style does not always help to get it; especially when the docs start saying The previous sections are pretty confusing and try to clarify things with some short examples. Thank goodness there are more resources. Especially parts of an article nHibernate made simple by David Veeneman gave me some "aha-erlebnisse". Here I will recap parts of what I have learned and used for one of my own projects. I have picked this part as well because it does a great job in showing that OR mapping in nHibernate is more than just writing an hbm.xml mapping for every database table.

Mapping collections

There are several ways to describe a collection

  • A set contains unique items, the items have no order
  • A bag can contain the same item more than once, the items have no order
  • A list contains ordered items. Each item has an index in the list.

An nHibernate mapping of a collection can even be in four different forms. Besides these three ways there is a map. A list has an integer index; a map is a list where the index has a complex type.

These collection types map not that well on the .NET types. There is no set type in .NET and the idea of ordering is implicit. Included in the nHibernate api is Iesi.Collections which does have a set type. But nHibernate can map on just the standard .NET types. For now I will do just that. A set can be mapped to an IEnumerable the others can be mapped to an Ilist. Note that the latter implicitly introduces an order to the items in a bag.

Mapping a collection in a domain

I have a website which contains a listing of publications. These publications are categorized in a number of subjects. Every publication can be on several subjects. A clear domain model in code will look like this

namespace Gekko.Website.Domain
{
    public class Subject
    {
        public override string ToString()
        {
            return Name;
        }

        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
    }
}

namespace Gekko.Website.Domain
{
    public class Publication
    {
        public override string ToString()
        {
            return Title;
        }

        public virtual int Id { get; set; }
        public virtual bool Hidden { get; set; }
        public virtual string Title { get; set; }
        public virtual string Description { get; set; }
        public virtual string Url { get; set; }
        public virtual bool InDutch { get; set; }
        public virtual Publisher PublishedBy { get; set; }
        public virtual int PublishedInYear { get; set; }
        private IList onSubjects = new List();
        public virtual IList OnSubjects
        {
            get { return onSubjects; }
            set { onSubjects = value; }
        }
    }
}

This is a model I can communicate in a clear language with my customer. As stated a publication has a list of subjects.

When it comes to storing this in a relational database things are more complicated. This is a many to many relation, a publication has several subjects, a subject has several publications. To store this we need three tables. The database will look like this

The good thing about using nHibernate as an O-R mapper is that I don't need a class for the PublicationOnSubject table. The relation between publication and subject, which is so clear in the model, can be mapped to the underlying database as a collection. The mapping of the subject is straightforward

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Gekko.Website.Domain" namespace="Gekko.Website.Domain">
  <class name="Subject" table="Subjects" proxy="Subject">
    <id name ="Id" type="Int32" column="idSubject">
      <generator class="identity"></generator>
    </id>
    <property name="Name" type="string" length="50" column="Subject"></property>
  </class>
</hibernate-mapping>

In the mapping of the publication the subjects are mapped in a many-to-many collection

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Gekko.Website.Domain" namespace="Gekko.Website.Domain">
  <class name ="Publication" table="Publications" proxy="Publication">
    <id name="Id" type="Int32" column="idPublication">
      <generator class="identity"></generator>
    </id>
    <property name="Hidden" type="boolean" column="Hidden"></property>
    <property name ="Title" type="string" length="80" column="Title"></property>
    <property name="Description" type="string" length="150" column="Description"></property>
    <property name="PublishedInYear" type="Int32" column="PublishedInYear"></property>
    <property name ="Url" type="string" length="120" column="Url"></property>
    <property name="InDutch" type="boolean" column="inDutch"></property>
    <many-to-one name="PublishedBy" class="Publisher" column="idPublisher"></many-to-one>
    <bag name="OnSubjects" table="PublicationOnSubject" lazy="false" >
      <key column="idPublication"></key>
      <many-to-many class="Subject" column="idSubject"></many-to-many>
    </bag>
  </class>
</hibernate-mapping>

OnSubjects is a bag which uses the table PublicationOnSubject. The key to this linking table is idPublication. The many to many class is the Subject class we just mapped.

Using the domain objects on a winform

The Publication and Subject domain objects are easy and clear to use in code. The OnSubjects bag of a publication maps to an Ilist and anything which implements Ilist can be used for databinding. In both classes I have overridden the ToString method to a descriptive property. This will make databinding a snap as the default display of an object is ToString().

This is some winforms code which combines controller and view. It uses the repositorymanger I presented in an earlier post. When loading the form a list of publications and a list of subjects are read from the repositories and bound directly to the controls. The listbox will display the Names of Subjects.

repository = new HibernateBasedRepository();
dataGridView1.DataSource = repository.Publications.ListAll("Title");
listBoxPotentialCategories.DataSource = repository.Subjects.ListAll("Name");

Likewise I can bind the OnSubjects property of the selected publication to another listbox

listBoxAssignedCategories.DataSource = pub.OnSubjects;

Things get even better when updating the OnSubjects property. Which is a matter of picking a Subject domain object, adding that to the OnSubjects collection of the publication and persisting the publication to the repository.

pub.OnSubjects.Add(listBoxPotentialCategories.SelectedItem as Subject);
repository.Publications.Save(pub);
repository.Commit();

Deleting a subject is a matter of removing the subject form the OnSubjects collection

pub.OnSubjects.Remove(listBoxAssignedCategories.SelectedItem as Subject);
repository.Publications.Save(pub);
repository.Commit();
The hard thing is updating the form by notifying the datasource has changed. For the sake of completeness:
((CurrencyManager) listBoxAssignedCategories.BindingContext[listBoxAssignedCategories.DataSource]).Refresh();

I can't make that easier, that's just winforms.

Winding down

That's about it. We have been working with a clear model which an end user also understands. In my experience most people do understand how data in information systems is organized in tabular form. But when I start talking about normalized tables we lose each other. Here nHibernate's O-R mapping pushes the technical details of normalization out of the model, even completely out of the code. And also for the developer writing the code work has become clearer and easier. Would you like to write out all sql needed by hand ? Not me.


Published May 29 2008, 02:23 PM by pvanooijen
Filed under:

Comments

Dew Drop - May 29, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - May 29, 2008 | Alvin Ashcraft's Morning Dew

# May 29, 2008 9:35 AM

Tobin Harris said:

Nice post - and a nice explanation of where many-many can make life easier!

It's worth mentioning that these join tables can be entities waiting to happen! These days I often find myself simply skipping the many to many mapping, and exposing the join as a first-class entity in the domain model (with simpler one-many associations too).

# May 29, 2008 11:45 AM

Erik said:

One little niggle: "There is no set type in .NET" - This is incorrect as of .NET 3.5 - see HashSet<T> - msdn.microsoft.com/.../bb359438.aspx

# May 29, 2008 12:22 PM

Reflective Perspective - Chris Alcock » The Morning Brew #104 said:

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #104

# May 30, 2008 3:35 AM

Deepak said:

Thanks,

I would like to extend this scenario by placing a "status" field in the PublicationOnSubject table, which will reflect the status of the publication on a department basis.

So, I can have a Publication with many Subjects, each subject having a different status.

Could you please let me know how to achieve the additional status mapping here please?

Deepak

# August 8, 2008 9:00 AM

pvanooijen said:

@Deepak : The moment the table is going to contain real properties and not just id's to link two other tables you will need to write mappings for the table.

# September 3, 2008 1:59 PM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add
Check out Devlicio.us!

Our Sponsors

Free Tech Publications

This Blog

Syndication

News