NHibernate Part 4: Mapping techniques for aggregation – One-To-Many mapping

Updated code available for NHibernate 2.0.1.GA here.
The toolingset used is Visual Studio 2008 and Sql Server 2008

Well, here it is: Part 4 in my experimentation series on NHibernate.

In this post I will continue with mapping strategies for aggregation and discuss the following form of aggregation:

  • one-to-many: an object uses a collection of objects of another type

But first I would like to eplain to you some more semantics on collections.

Sets, Bags and other types of collections

In a database, a one-to-many mapping is simply a table with a primary key for the one side and a second table with a field referencing the primary key of the first table:

But in our C# code we have more options. Depending on the type of collection we can have following semantics:

  • You can have a collection of items without any key to retrieve them from the collection. In that collection all elements can be unique (a Set) or elements can be duplicated (a Bag or Multiset).
  • You can have a collection of items with a key to retrieve them from the collection. This key can simply be an integer (in which case we refer to as being a List) or may also be another simple type or even an object (in which case we refer to it as being a Dictionary).

Multiplicity of aggregation: One to Many mapping

The Code

using Bag semantics

This may well be the simplest one-to-many mapping there is: you have an object and you add it to the collection without any special rules.

On the code side:

Passing collections to NHibernate

You will see in the code samples that I always use the interface of a collection type and never directly the collection as a property of an object. This is a requirement of NHibernate, because it internally uses it’s own representations of the collections.

public class Customer
{
   private int id;
   private IList orderList;

   public int Id 
   {
      get { return this.id; }
      set { this.id = value; }
   }

   // Simply pass the interface of the collection
   public IList OrderList
   {
      get { return this.orderList; }
      set { this.orderList = value; }
   }
}

public class Order
{
   private int id;

   public int Id 
   {
      get { return this.id; }
      set { this.id = value; }
   }
}

On the mapping side:

For the Order class no special tags are necessary and we use the usual mapping tags. You can find them in the sample code

Using keywords as database object names
If you look at the sample code, you will notice something different about the mapping file. It doesn’t have anything to do with the subject at hand however, but rather with the name of the table: “order”. Those of you familiar with SQL will know that this is also an SQL keyword and thus can normally not be used as database object name. Most databases however support some type of quoting the name, thus making it possible to use that name anyway. NHibernate abstracts this quoting by allowing you to use backticks as a unified way of doing this.

<class name="UniDirectional.UOrder, 
         NHibernateOneToMany" 
      table="`order`">
   <!-- usual mapping members -->
</class>


NHibernate will replace the single quotes with the quote type used by your underlying database engine.

The Customer class uses the <bag> tag for its collection property:

<class name="UniDirectional.Customer, NHibernateOneToMany" 
      table="customer">
   <!-- The usual things like an id tag -->
   <bag name="OrderList" cascade="all">
      <key column="CustomerId"/>
      <one-to-many 
         class="UniDirectional.UOrder, NHibernateOneToMany"/>
   </bag>
</class>


What we are saying here is the following:
The OrderList property of this object has Bag semantics and is filled with instances of the class UniDirectional.Order. You can find the instances by looking in the mapped table for UniDirectional.Order and find records with the field CustomerId having value equal to this objects id value.

On the database side:

CREATE TABLE `customer` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `Name` varchar(45) NOT NULL default '',
   PRIMARY KEY  (`Id`)
);

CREATE TABLE `order` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `Number` int(10) unsigned NOT NULL default '0',
   `CustomerId` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`Id`)
);


The field CustomerId in the table order receives the id value of the associated record in the customer table.

To make the mapping bidirectional:

To make the mapping bidirectional you must specify a <many-to-one> tag in the child mapping file.

<many-to-one name="OrderedBy" 
   class="BiDirectional.BCustomer, NHibernateOneToMany" 
   column="CustomerId"/>


What we are saying here is the following:
The OrderedBy property of this object is the object of type BiDirectional.BCustomer and with the Id-value in the column “CustomerId”.

When trying this bidirectional mapping I thought it would be made by combining a <one-to-many> with a <one-to-one>. So that is what I tried, but without any luck. You must use a <many-to-one>. This doesn’t seem logical to me: the Order object in the sample above is ordered by only one customer, so it has a one-to-one relationship with that customer: the customer made that order and that order was ordered by that customer.

using Set semantics

Nothing difficult here:

  • Replace all IList with ISet
  • Replace all System.Collections.ArrayList with Iesi.Collections.ListSet
  • Replace all <bag> with <set>

On the code side:

On the code side we get following:

using Iesi.Collections;

// Only members important for this example are displayed.
// Look at the sample code for a complete example
public class Dog
{
   private ISet legs;

   // Simply pass the interface of the collection
   public ISet Legs
   {
      get { return this.legs; }
      set { this.legs= value; }
   }
}

On the mapping side:

<set name="Legs" cascade="all">
   <key column="DogId"/>
   <one-to-many class="Leg, NHibernateOneToMany"/>
</set>

Mapping enumerations

As you will notice in the sample code, I use an enumeration for the property of a class. And as you will see in the mapping file, nothing special has to be done for this: you simply map it to a field with a type of the underlying enumeration type.

However, it is possible to map the enumeration to a string presentation of the enumeration value. How to do this can be seen in this blogpost of Jeremy Miller.

On the database side:

Nothing changes on the database side.

using List semantics

On the code side:

On the code side, nothing changes with respect to the example demonstrating <bag>-semantics

On the mapping side:

For the mapping we use the <list> tag as follows:

<class name="UniDirectional.Conversation, NHibernateOneToMany" 
      table="conversation">
   <!-- The usual things like an id tag -->
   <list name="MessageList" cascade="all">
      <key column="ConversationId"/>
      <index column="`Order`"/>
      <one-to-many 
         class="UniDirectional.Message, NHibernateOneToMany"/>
   </bag>
</class>


What we are saying here is the following:
The MessageList property of this object has List semantics and is filled with instances of the class UniDirectional.Message. You can find the instances by looking in the mapped table for UniDirectional.Message and find records with the field ConversationId having value equal to this objects id value. The index (and thus position) of the element in the list is specified by the value in the Order field of the table.

On the database side:

The parent table which stores the Converstation objects is a classic table with fields for the Id and the other classic properties. It will not be shown here.
The child table for the Message objects has following structure:

CREATE TABLE `message` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `Order` int(10) unsigned NOT NULL default '0',
   `Text` varchar(45) NOT NULL default '',
   `ConversationId` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`Id`)
);


As you can see, there is not much difference with the normal <bag> semantics table, except for the extra Order field of type integer to store the index of the object in the List.

Inside the database:

If you look inside your database you’ll see that the Order field was filled with a numerical sequential value which is the index of the UMessage objects in the UConversation MessageList property:

onetomany_list_index.jpg

This corresponds with the semantics of the List tag: use the index of an item in the list as a key to retrieve the item. So the position of the item in te list is important.

using Dictionary semantics

When using dictionary semantics to store an object we have two possibilities:

  1. The key can be a simple type like a String
  2. The key can be a complex type that is an object or entity

Dictionary semantics with simple keys

The type of the key used in this sample is string. You are of course free to use other simple types like integers. They are similar in usage.

On the code side:

On the code side we get following:

public class UEmployee
{
   // Just the usual stuff
}


using System.Collections;

public class UDepartment
{
   // Just the members needed for the 
   //   dictionary are shown
   private IDictionary employeeList;

   public IDictionary EmployeeList 
   {
      get { return this.employeeList; }
      set { this.employeeList = value; }
   }
}

On the mapping side:

The UEmployee class needs no special tags, we just use the usual ones.

The UDepartment class has the usual tags too, except for the dictionary property:

<map name="EmployeeList" cascade="all">
   <key column="DepartmentId"/>
   <index column="Name" type="String"/>
   <one-to-many 
      class="UniDirectional.UEmployee, NHibernateOneToMany"/>
</map >


What we are saying here is the following:
The EmployeeList property of this object has Map semantics and is filled with instances of the class UniDirectional.UEmployee. You can find the instances by looking in the mapped table for UniDirectional.UEmployee and find records with the field DepartmentId having value equal to this objects id value. The key used in the map is of type String and can be found in the column Name of the table used to store the UEmployee objects.

On the database side:

CREATE TABLE `department` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `Name` varchar(45) NOT NULL default '',
   PRIMARY KEY  (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


CREATE TABLE `employee` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `Name` varchar(45) NOT NULL default '',
   `Address` varchar(45) NOT NULL default '',
   `DepartmentId` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


The field Name in the table employee receives the key value of the entry in the dictionary.

On the usage site:

Using a collection with dictionary semantics and simple typed keys goes like this:

UniDirectional.UEmployee employee1 = 
   new UniDirectional.UEmployee();
employee1.Name = "John";
employee1.Address = "Heaven";
UniDirectional.UEmployee employee2 = 
   new UniDirectional.UEmployee();
employee2.Name = "Paul";
employee2.Address = "Somewhere in England";
Hashtable employeeList = new Hashtable();
employeeList.Add(employee1.Name, employee1);
employeeList.Add(employee2.Name, employee2);

UniDirectional.UDepartment someDepartment = 
   new UniDirectional.UDepartment();
someDepartment.Name = "Music";
someDepartment.EmployeeList = 
   (IDictionary)employeeList;

session.Save(someDepartment);

As implementation of the dictionary, we use a HashTable and add the items using the appropriate key.

To make the mapping bidirectional:

According to the NHibernate documentation, these collections can not be made bidirectional.

Dictionary semantics with object or entity keys

On the code side:

To support entities as keys, we need three classes:

  1. The class whose instantiated objects represent the keys in the dictionary. This class needs its GetHashCode() method to be overridden. How to do this can be found here
  2. The class whose instantiated objects represent the items to be saved in the dictionary
  3. The class whose instantiated objects contain the dictionary

On the code side we get following:

// This class represents the items to be stored
//   in the dictionary
public class UHabitant
{
   // Just the usual stuff
}


// This class represents the keys used to identify the items 
//   stored in the dictionary
public class UPersonName
{
   // Just the usual stuff
		
   // Following members are needed to be able to 
   //   use objects of this class as keys in a HashTable
   public override bool Equals(object obj)
   {
      // Check for null values and compare run-time types.
      if (obj == null || GetType() != obj.GetType()) 
         return false;
      UPersonName asName = (UPersonName)obj;
			
      if (name.Equals(asName.name) && id.Equals(asName.id))
         return true;
			
      return false;
   }
		
   public override int GetHashCode()
   {
      return (name + id).GetHashCode();
   }
}


// This class represents contains the dictionary
using System.Collections;

public class UStreet
{
   // Just the members needed for the 
   //   dictionary are shown
   private IDictionary habitantList;

   public IDictionary HabitantList
   {
      get { return this.habitantList; }
      set { this.habitantList= value; }
   }
}

On the mapping side:

The UHabitant and UPersonName classes need no special tags, we just use the usual ones.

The UStreet class has the usual tags too, except for the dictionary property:

<map name="HabitantList" cascade="all">
   <key column="StreetId"/>
   <index-many-to-many column="NameId" 
      class="UniDirectional.UPersonName, 
                   NHibernateOneToMany"/>
   <one-to-many 
      class="UniDirectional.UHabitant, NHibernateOneToMany"/>
</map >


What we are saying here is the following:
The HabitantList property of this object has Map semantics and is filled with instances of the class UniDirectional.UHabitant. You can find the instances by looking in the mapped table for UniDirectional.UHabitantand find records with the field StreetIdhaving value equal to this objects id value. The key used in the map is of type UPersonName and can be found by searching the table used to store the UPersonName objects for entries with an id equal to the value in the column NameId.

On the database side:

CREATE TABLE `personname` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `Name` varchar(45) NOT NULL default '',
   PRIMARY KEY  (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


CREATE TABLE `habitant` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `NameId` int(10) unsigned NOT NULL default '0',
   `StreetId` int(10) unsigned NOT NULL default '0',
   `HouseNumber` int(10) unsigned NOT NULL default '0',
   PRIMARY KEY  (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


CREATE TABLE `street` (
   `Id` int(10) unsigned NOT NULL auto_increment,
   `Name` varchar(45) NOT NULL default '',
   PRIMARY KEY  (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


The field NameId in the table habitant receives the value of the field Id in the table personname.

On the usage site:

Using a collection with dictionary semantics and entity keys goes like this:

UniDirectional.UHabitant habitant1 = 
   new UniDirectional.UHabitant();
habitant1.HouseNumber = 10;
UniDirectional.UPersonName name1 = 
   new UniDirectional.UPersonName();
name1.Name = "John";
UniDirectional.UHabitant habitant2 = 
   new UniDirectional.UHabitant();
habitant2.HouseNumber = 15;
UniDirectional.UPersonName name2 = 
   new UniDirectional.UPersonName();
name2.Name = "Paul";
Hashtable habitantList = new Hashtable();
habitantList.Add(name1, habitant1);
habitantList.Add(name2, habitant2);

UniDirectional.UStreet someStreet = 
   new UniDirectional.UStreet();
someStreet.Name = "AbbeyRoad";
someStreet.HabitantList = (IDictionary)habitantList;

session.Save(name1);
session.Save(name2);
session.Save(someStreet);

As implementation of the dictionary, we use a HashTable and add the items using the appropriate key.
Watch this code very carefully: as you can see you first need to store the keys separately and then you store the object having the dictionary.

Why, oh why?
I have been struggling with saving a dictionary with entity keys.

First, I did not save the keys before saving the object with the dictionary. But when I did that NHibernate kept throwing an exception stating the following:

object references an unsaved transient instance – save the transient instance before flushing: <name of the object>

Then, when I looked in the log file created by NHibernate, I could not find an entry showing that NHibernate saves the object serving as keys. So I save the keys before saving the dictionary and then everything works fine.

But why doesn’t NHibernate save the entity keys of a dictionary?

To make the mapping bidirectional:

According to the NHibernate documentation, these collections can not be made bidirectional.

Updates

3 September 2006: original version
20 October 2006: finished Using Dictionary semantics

26 thoughts on “NHibernate Part 4: Mapping techniques for aggregation – One-To-Many mapping

  1. Your breakdown of the different parts that make associations work is concise and complete. This is some of the most helpful documentation on nhibernate around! Thanks.

  2. Hi,
    I want to only read the list of items in one-to-many mapping which is being done without error. But I dont want to insert/update/delete the list when saving/updating the master object. I have set the cascade=”none” but of no use.
    Do you know the solution to this?

  3. Thanks a lot for these posts; they’ve saved me a lot of pain.

    One thing I haven’t see much on is how to map 0..1 associations. For instance, I have a Person that has a Name object embedded in it. The Name attributes are saved in a different table and the Person references the Name by foreign key (name_id) which can be null.

    Person+——-Name

    I will appreciate your thoughts on this.

    Thanks

  4. Hi Kwaku,

    unfortunately I had the same problem. Even the NHibernate documentation itself is dead-silent about this.

    I do not have the possibility to experiment right now, but I also wonder of this doesn’t “just work”? If you use a many to one mapping, but do not provide a “many” object (like a car with no parkingplace in the above examples), doesn’t it “just work”?

    I anyone found a solution, please let me know.

  5. Nive post. however, it seems like the unidirectional one-to-many sample does no longer work in the current release of nhibernate and you’ll always get an exception since the sql generated does not include the external key column….

  6. hey thanks, this was killing me – all examples except yours were missing pieces – to understand what is going on requires – database definitions, xml mapping files, class definition, and code that uses these. All examples on hibernate were missing one of more of above for parent/child relationship. This was terrific, thanks again.

  7. Reply against a comment by Kashif December 21, 2006 at 12:38

    “Hi,
    I want to only read the list of items in one-to-many mapping which is being done without error. But I dont want to insert/update/delete the list when saving/updating the master object. I have set the cascade=”none” but of no use.
    Do you know the solution to this”

    I had a similar problem and my analysis was pretty much similar to yours that the cascade attribute has no impact.

    The solution that i have implemented is that for insert and update operations of child elements i am doing the same on child elements such as create(new child) or update(old child).

    To avoid the operations on master having any impact on child i do the following:

    1. set the child to null such as master.setChildren(null) before calling create(master) or update(master).
    2. the setting i have in hbm for master object is :

    this worked fine for my requirements. Hope this helps you as well.

    Regards,
    Rohit

  8. Not sure why the mapping part didn’t get added to my post. Here it is:

    <set name="master" cascade="none">
    <key column="master_id" update="false"/>
    <one-to-many class="Child" />
    </set>

    Regards,
    Rohit

  9. Oh dear! I’m trying to follow this in order to understand my own failing application (using a one-to-many set), but I find your example code impossible to follow, switching as it does between customer and orders (that you’ve given the class and database structure for) then to dogs and legs (the particular model I’m trying to follow, where the database is “unchanged” LOL), conversations and messages, employees and departments etc… I’ve been battling sketchy NHibernate documentation for three days now *sigh*

    Other than that, I guess this is a great (and needed!) document, for which I thank you.

  10. Is it necessary to put a inverse=”true” anywhere?

    >To make the mapping bidirectional:
    >
    >To make the mapping bidirectional you must specify a tag in >the child mapping file.
    >
    > class=”BiDirectional.BCustomer, NHibernateOneToMany”
    > column=”CustomerId”/>

  11. This is all very interesting! What I’m having difficulties with now, though, is that I’m trying to use a generic IDictionary instead of the old non-generic one, and I only seem to be running into the following exception:

    “NHibernate.MappingException: property mapping has wrong number of columns”

    If I reduce the number of columns/properties in the object I’m storing in the dictionary, it all works just fine. But it seems rather silly to only be able to have two-column objects in a dictionary. I think it’s somehow related to how the dictionary and the objects within it are configured, but I’m not sure how they should be properly defined.

    I’m using Castle Project’s ActiveRecord, but it’s NHibernate that’s throwing the exceptions, so I need to figure out how to do this with NHibernate before I can start figuring out what ActiveRecord or I might be doing wrong. I’ve decorated the dictionary property with the following attribute:

    [HasMany(Index = “Name”, RelationType = RelationType.Map)]

    “Name” is a string property on my object and is supposed to be the key in the dictionary. I have a number of other properties in my object and if I reduce the properties to just this “Name” property and one other, it works. If I add the other properties back, I get the above-mentioned exception.

    Do you have a working example of how to use a keyed generci dictionary with NHibernate? If I saw something that worked there, I might be able to figure out how to translate it into ActiveRecord.

  12. Hello,

    I would like to take to this opportunity to ask a question about one-to-many. I have two classes:

    GeneralInformation and famousPlacesInLondon

    {{

    public class GeneralInformation
    {

    private int id;

    public virtual int Id
    {
    get { return id; }
    set { id = value; }
    }

    private IList famousPlacesOutLondon;

    public virtual IList FamousPlacesOutLondon
    {
    get { return famousPlacesOutLondon; }
    set { famousPlacesOutLondon = value; }
    }
    }

    public class FamousPlacesInLondon
    {
    private int id;

    public virtual int Id
    {
    get { return id; }
    set { id = value; }
    }
    private string link;

    public virtual string Link
    {
    get { return link; }
    set { link = value; }
    }
    private string title;

    public virtual string Title
    {
    get { return title; }
    set { title = value; }
    }

    }
    }

    }}

    My mappings are as follows:

    {{

    }}

    If I fill the GeneralInformation with data and do a save using NHibernate, everything gets inserted into the appropriate table, except the ForeignKey GeneralInformationId. What is wrong?

    TIA
    Yaz

  13. The reason you have to use a many-to-one for the Order and Customer table is because one customer can have many orders. So, to diagram:

    Customer Order

    Since you’re adding the property to the Order entity, you follow the relationship from the other side:

    Order Customer

    Thus, the attribute you use is many-to-one.

  14. Er, the comment system ate my diagrams.

    First Diagram is this:

    Customer «1—-M» Order

    Second Diagram is this:

    Order «M—-1» Customer

Leave a reply to Sam Cancel reply