NHibernate Mapping - <list/>

time to read 7 min | 1214 words

I am not going to talk about all the options that NHibernate has for collections, I already did it for <set/>, and most of that are pretty similar. Instead, I am going to show just the unique stuff about NHibernate’s <list//>. While <set/> is an unordered collection, of unique elements, a <list/> is a collection of elements where the order matter, and duplicate elements are allowed (because there is a difference between item A in index 0 and index 4).

Let us look at what it means by looking at the class itself:

public class User
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Phone> EmergencyPhones { get; set; }
}

And the mapping:

<class name="User" table="Users">
    <id name="Id">
        <generator class="hilo"/>
    </id>
    <property name="Name"/>
    <list name="EmergencyPhones" table="UsersToEmergencyPhones" cascade="all">
        <key column="UserId"/>
        <index column="Position"/>
        <many-to-many column="PhoneId" class="Phone"/>
    </list>
</class>

Note that I am using <many-to-many/> mapping, if I wanted to use the <one-to-many/> mapping, the PhoneId would be located on the Phones table. Now, let us see how we are working with it:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    session.Save(new User
    {
        Name = "a",
        EmergencyPhones = new List<Phone>
        {
            new Phone{Name = "Pop", Number = "123-456-789"},
            new Phone{Name = "Mom", Number = "456-789-123"},
            new Phone{Name = "Dog", Number = "789-456-123"},
        }
    });
    tx.Commit();
}

This will produce the following SQL:

image

And now, what happen if we want to read it? Here is the code:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    var user = session.Get<User>(id);
    foreach (var emergencyPhone in user.EmergencyPhones)
    {
        Console.WriteLine(emergencyPhone.Number);
    }
    tx.Commit();
}

And the generated SQL:

image 

What happen if we update the list? Let us see the code (anyone is reminded in CS 101?):

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    var user = session.Get<User>(id);
    user.EmergencyPhones.Add(new Phone{Name = "Cat", Number = "1337-7331"});
    var temp = user.EmergencyPhones[2];
    user.EmergencyPhones[2] = user.EmergencyPhones[0];
    user.EmergencyPhones[0] = temp;
    tx.Commit();
}

Which result in:

image

Beware of holes in the list! One thing that you should be careful about is something removing an entry from the list without modifying all the other indexes of the list would cause… problems.

If you were using Hibernate (Java), you would get an exception and that would be it. NHibernate, however, is much smarter than that, and will give you a list back, with one caveat. Let us say that we have the following data in the database:

image

Note that we don’t have a row in position #2. When we load the list, we will get: [ phone#3, phone#2, null, phone#4 ]

A lot of the time, we are processing things like this:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    var user = session.Get<User>(id);
    foreach (var emergencyPhone in user.EmergencyPhones)
    {
        Console.WriteLine(emergencyPhone.Number);
    }
    tx.Commit();
}

When this code will encounter a hole in the list, it is going to explode in an unpleasant way. You should be aware of this possibility and either protect yourself from it or make sure that whenever you delete from a list, you will rearrange the list so there are no holes in it.