Mapping Collections in NHibernate (Part 1)

Sorry, have been quiet for some time due to too much project work... But now lets discuss a fairly important and often misunderstood topic: the mapping of collections in NHibernate

Monitoring the NHUsers Google group I can see a lot of questions around collections and how to best map them in NHibernate. NHibernate offers several possibilities to map a collection of objects. You can use a Set, a Map, a Bag, an IdBag or a List. Let's first analyze what the ("exact" mathematical) definition of each of them is.

Theoretical background

  • A Set is a collection of distinct objects considered as a whole.
    A valid example of a set (of letters) is: { a, b, c, d }. Each letter occurs exactly once.
  • A Bag is a generalization of a set. A member of a bag can have more than one membership while each member of a set has only one membership.
    A valid example of a bag is { a, a, a, b, c, c, d, ...}. The letters a and c appear more than once in the Bag.
  • A Map (also called associative container, hash, dictionary, lookup table) is a abstract data type composed of a collection of keys and a collection of values, where each key is associated with one value. The relationship between a key and its value is sometimes called a mapping or binding.
    A valid sample would be a map of capitals of the world's countries where the country code is the key and the name of the capital is the value, i.e. { { "CH", "Bern" }, { "D", "Berlin" }, { "USA", "Washington" }, ...}
  • A List is collection of objects (also called elements or members). Each element in the List has an index. Similar to a Bag a member of the the list can have more than one membership.
    A valid sample of a list is e.g. { {1, "Bob"}, {2,"Sue"}, {3,"Ann"}, {4,"Sue"}, ...}. Obviously the name "Ann" occurs twice, once with the index 2 and once with the index 4.

.NET and the CLR

The CLR of .NET only provides us Maps and Lists. Maps are either implemented as Hashtables (non generic) or Dictionaries (generic). Lists are available as ArrayList (non generic) or List<T> (generic). But we have no direct representation of Set and Bag in the CLR. A Bag can easily be simulated by using a List. We just have the superfluous index. It is not obvious why Microsoft didn't implement a Set though. Since NHibernate makes heavy use of sets they provide an implementation of Sets in the so called IESI collection library which is part of the stack.

Samples

The code to this samples can be downloaded from here.

Collection of values

Let's take a simple sample. A customer provides several possibilities to contact him (e.g. Mobile, Office phone, EMail, FAX, etc.). We can represent this list of possible contacts as a Collection of strings.

image

Obviously we want each contact to be unique. Thus a Set would certainly be a good choice for our Contacts collection. The corresponding code is

using Iesi.Collections.Generic;
 
namespace CollectionMapping
{
    public class Customer
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual ISet<string> Contacts { get; set; }
 
        public Customer()
        {
            Contacts = new HashedSet<string>();
        }
    }
}

Note the import of the Iesi.Collections.Generic namespace which is implemented in the Iesi.Collections assembly which is part of the NHibernate stack. Note also that in the constructor we initialize the collection. A possible implementation for the ISet interface is the HashedSet<T> also found in the IESI collection library.

Let's have a look at the NHibernate mapping now

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="CollectionMapping"
                   namespace="CollectionMapping">
  <class name="Customer">
    <id name="Id">
      <generator class="native"/>
    </id>
    <property name="Name"/>
    
    <set name="Contacts">
      <key column="CustomerId" foreign-key="fk_Contact_Customer"/>
      <element column="Description" type="String"/>
    </set>
    
  </class>
</hibernate-mapping>

The schema generation script produced by the SchemaExport service of NHibernate is then (for SQL Server 2005)

create table Customer (Id INT IDENTITY NOT NULL, Name NVARCHAR(255) null, primary key (Id))
 
create table Contacts (CustomerId INT not null, Description NVARCHAR(255) null)
 
alter table Contacts add constraint fk_Contact_Customer foreign key (CustomerId) references Customer

(For a detailed discussion of Schema Generation see this post.)

Now we can write a unit test to verify we can indeed create customer objects with contacts

[Test]
public void Can_create_customer_with_contacts()
{
    var customer = new Customer {Name = "John Doe"};
    customer.Contacts.Add("Business phone: 123-12 34 56");
    customer.Contacts.Add("Mobile: 555-72 44 55");
    customer.Contacts.Add("Email: john.doe@somecompany.com");
 
    Session.Save(customer);
    Session.Flush();
    Session.Clear();
 
    // Assertions
    var fromDb = Session.Get<Customer>(customer.Id);
    Assert.AreNotSame(customer, fromDb);
    Assert.AreEqual(customer.Name, fromDb.Name);
    Assert.AreEqual(customer.Contacts.Count, fromDb.Contacts.Count);
}

which will produce the following sql commands (note: I'm using SQL Light here as my in memory test database. A detailed discussion about setting up an environment for TDD can be found in this post.)

NHibernate: INSERT INTO Customer (Name) VALUES (@p0); select SCOPE_IDENTITY(); 
@p0 = 'John Doe'
NHibernate: INSERT INTO Contacts (CustomerId, Description) VALUES (@p0, @p1); 
@p0 = '1', @p1 = 'Business phone: 123-12 34 56'
NHibernate: INSERT INTO Contacts (CustomerId, Description) VALUES (@p0, @p1); 
@p0 = '1', @p1 = 'Mobile: 555-72 44 55'
NHibernate: INSERT INTO Contacts (CustomerId, Description) VALUES (@p0, @p1); 
@p0 = '1', @p1 = 'Email: john.doe@somecompany.com'
NHibernate: SELECT customer0_.Id as Id0_0_, customer0_.Name as Name0_0_ 
FROM Customer customer0_ WHERE customer0_.Id=@p0; @p0 = '1'
 
NHibernate: SELECT contacts0_.CustomerId as CustomerId0_, 
contacts0_.Description as Descript2_0_ 
FROM Contacts contacts0_ 
WHERE contacts0_.CustomerId=@p0; @p0 = '1'

So far so good. We have a solution where each customer object has a collection of unique contacts. The contacts are just a collection of strings. Now maybe we want a contact to be a complex object with several properties instead of only a simple string.

Collection of (complex) objects

Let's assume our contact should contain a type and a description property. And let's call the collection of contacts "BusinessContacts".

 

image

 

We can define our customer and contact classes as follows

using Iesi.Collections.Generic;
 
namespace CollectionMapping
{
    public class Customer
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual ISet<Contact> BusinessContacts { get; set; }
 
        public Customer()
        {
            BusinessContacts = new HashedSet<Contact>();
        }
    }
 
    public class Contact
    {
        public virtual ContactTypes Type { get; set; }
        public virtual string Description { get; set; }
    }
 
    public enum ContactTypes
    {
        Undefined=0,
        Email,
        Phone,
        Mobile,
        Fax
    }
}
Note that the Contacts are still treated as value objects (in a DDD sense) and not entities. Thus a contact does not have an ID.

Equality and uniqueness

Now we are sure that each distinct contact can only be added to the Contacts collection once. So trying to (accidentally) add the work phone twice would result in a exception. But wait a moment! How does the Set know that two instances of type Contact are the same? Now, what does equality mean? The (online) help of Microsoft tells us that:

"In C#, there are two different kinds of equality: reference equality and value equality. Value equality is the generally understood meaning of equality: it means that two objects contain the same values. For example, two integers with the value of 2 have value equality. Reference equality means that there are not two objects to compare. Instead, there are two object references and both of them refer to the same object."

And then they add:

"Because Equals is a virtual method, any class can override its implementation. Any class that represents a value, essentially any value type, or a set of values as a group, such as a complex number class, should override Equals."

Well, internally the Set uses value equality (that is the Equals function) to differentiate objects. In our case we want to say that two (different) instances of type Contact are equal if their property Description and Type contain the same values. Thus we have to override the Equals function and provide our own implementation. But when overriding the Equals function one also has to override the GetHashCode function at the same time!

Since the type System.String and System.Int32 (an enum is by default a System.Int32) already provide an Equals and a GetHashCode implementation we take this one and thus the code to add to our Contacts class is fairly simple

public override bool Equals(object obj)
{
    if(obj == null || !(obj is Contact)) return false;
    var contact = (Contact)obj;
    return ((Description == null && contact.Description == null) || 
        Description.Equals(contact.Description)) && Type.Equals(contact.Type);
}
 
public override int GetHashCode()
{
    return string.Format("{0}|{1}", Type, Description).GetHashCode();
}
Note that to get the hash code we first convert the two properties into a unique string and then take it's default GetHashCode implementation. This might not be the most efficient solution but it certainly gives me the correct results.

Having done so we can be sure that any distinct contact is never added more than once to the Contacts collection of the Customer object.

Now let's have a look at the mapping of the classes

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="CollectionMapping"
                   namespace="CollectionMapping">
  <class name="Customer">
    <id name="Id">
      <generator class="native"/>
    </id>
    <property name="Name"/>
 
    <set name="BusinessContacts" table="BusinessContact">
      <key column="CustomerId" foreign-key="fk_BusinessContact_Customer"/>
      <composite-element class="Contact">
        <property name="Type" not-null="true"/>
        <property name="Description" not-null="true"/>
      </composite-element>
    </set>
    
  </class>
</hibernate-mapping>

Now we have a <composite-element> instead of just an <element> in the mapping file. The <composite-element> can contain as many <property> child nodes as needed.

The schema generation script produced by the SchemaExport service of NHibernate is then (for SQL Server 2005)

create table Customer (Id INT IDENTITY NOT NULL, Name NVARCHAR(255) null, primary key (Id))
create table BusinessContact (CustomerId INT not null, Type INT null, Description NVARCHAR(255) null)
alter table BusinessContact add constraint fk_BusinessContact_Customer foreign key (CustomerId) references Customer

Again we can write a unit test to verify we can indeed create customer objects with business contacts

[Test]
public void Can_create_customer_with_business_contacts()
{
    var customer = new Customer {Name = "John Doe"};
    customer.BusinessContacts.Add(new Contact
                                      {
                                          Type = ContactTypes.Phone,
                                          Description = "123-12 34 56"
                                      });
    customer.BusinessContacts.Add(new Contact
                                      {
                                          Type = ContactTypes.Mobile,
                                          Description = "555-72 44 55"
                                      });
    customer.BusinessContacts.Add(new Contact
                                      {
                                          Type = ContactTypes.Email,
                                          Description = "john.doe@somecompany.com"
                                      });
 
    Session.Save(customer);
    Session.Flush();
    Session.Clear();
 
    // Assertions
    var fromDb = Session.Get<Customer>(customer.Id);
    Assert.AreNotSame(customer, fromDb);
    Assert.AreEqual(customer.Name, fromDb.Name);
    Assert.AreEqual(customer.BusinessContacts.Count, fromDb.BusinessContacts.Count);
}

and NHibernate will produce the following output (for an SQL light database)

NHibernate: INSERT INTO Customer (Name) VALUES (@p0); select SCOPE_IDENTITY(); @p0 = 'John Doe'
NHibernate: INSERT INTO BusinessContact (CustomerId, Type, Description) VALUES (@p0, @p1, @p2); @p0 = '1', @p1 = 'Phone', @p2 = '123-12 34 56'
NHibernate: INSERT INTO BusinessContact (CustomerId, Type, Description) VALUES (@p0, @p1, @p2); @p0 = '1', @p1 = 'Mobile', @p2 = '555-72 44 55'
NHibernate: INSERT INTO BusinessContact (CustomerId, Type, Description) VALUES (@p0, @p1, @p2); @p0 = '1', @p1 = 'Email', @p2 = 'john.doe@somecompany.com'
 
NHibernate: SELECT customer0_.Id as Id0_0_, customer0_.Name as Name0_0_ 
FROM Customer customer0_ WHERE customer0_.Id=@p0; @p0 = '1'
NHibernate: SELECT businessco0_.CustomerId as CustomerId0_, businessco0_.Type as Type0_, businessco0_.Description as Descript3_0_ 
FROM BusinessContact businessco0_ WHERE businessco0_.CustomerId=@p0; @p0 = '1'

That's it for the moment. In my next post I'll discuss the Bag and the Map type of collections.

You can find the code for these samples here.

Enjoy!

Blog Signature Gabriel .

Print | posted on Thursday, June 12, 2008 9:39 PM

Comments on this post

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
.NET 3.5 introduces System.Collections.Generic.HashSet<T> now.
Left by Jason Stangroome on Jun 12, 2008 11:31 PM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Errata corrige:

A valid sample of a list is e.g. { {1, "Bob"}, {2,"Sue"}, {3,"Ann"}, {4,"Sue"}, ...}. Obviously the name "Ann" occurs twice, once with the index 2 and once with the index 4.

Maybe you did mean Sue and not Ann.
Left by Tommaso Caldarola on Jun 13, 2008 12:05 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Another great post - thanks! I have a question: why do collection properties in NHibernate or ActiveRecord always have setters?

ISet<Contact> BusinessContacts { get; set; }

Is this always necessary? I'm used to making mine read only and then adding to the property directly:

BusinessContacts.Add(new BusinessContact())

Thanks again

Sean
Left by Sean Kearon on Jun 13, 2008 2:56 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
@Sean: NHibernate tries to declare and populate the collection via the property setter. If you don't have a setter and want NHibernate to use the backing field instead then you have to declare it in the mapping file
Left by Gabriel Schenker on Jun 13, 2008 3:20 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
@Tommaso: you are right! Thanks for carefully reading...
Left by Gabriel Schenker on Jun 13, 2008 3:22 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
@Jason: to be honest, I was NOT aware of this. Thank you for informing me.
Left by Gabriel Schenker on Jun 13, 2008 3:22 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
In your next posting you might want to mention briefly about lazy-loading.
Left by Reddy on Jun 13, 2008 6:22 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Good article! But I have one minor nit to pick: it is not correct to say that "The CLR of .NET only provides us Maps and Lists". It is the Base Class Library (BCL) that only provided maps and lists. Putting it simply, the CLR is a virtual machine, while the BCL is just a set of libraries.

As Jason mentioned above, the BCL in .NET 3.5 contains a set implementation called HashSet. Further to my point above, its worth noting that this release of the .NET framework does not include a new version of the CLR, which is still at version 2.0 (and has been since .NET 2.0).

I think developers can easily get confused about the differences between .NET and the BCL and the CLR. When writing technical articles we should endeavour to be as clear as possible about these distinct concepts.
Left by Paul Batum on Jun 13, 2008 8:18 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
@Paul: I appreciate your clarification. Of course you are right. Next time I'll try to be more precise.
Left by Gabriel Schenker on Jun 13, 2008 11:05 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Well, internally the Set uses value equality (that is the Equals function) to differentiate objects. In our case we want to say that two (different) instances of type Contact are equal if their property Description and Type contain the same values.

I think you meant reference equality.
Left by alberto on Jun 14, 2008 9:26 PM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Sean, you can use a protected setter and NHibernate is happy.
Left by alwin on Jun 22, 2008 9:20 PM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Since this is a FAQ blog, I am going to ask a newbie question.

Is there a way to map enum info lookup table with foreign key constraints?

An example will be creating ContactTypes table while generating the schema.

Or at a higher level, how do others achieve this? Some DBA's want the ability to have the lookup tables in DB for writing queries.
Left by Harry on Jun 26, 2008 8:02 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
@Harry: this is a good question and is discussed to some extent in the yahoo group for "domain driven design" (DDD). E.g. in this thread:

http://tech.groups.yahoo.com/group/domaindrivendesign/message/7241

you might find there some other threads on the topic too. As always there doesn't exist a unique answer... I regret
Left by Gabriel Schenker on Jun 26, 2008 7:32 PM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
What if your business rules state that a Contract must be tied to a Customer?

This would suggest to me that the Contract constructor would need to contain a Custom entity but doing this would make the initialization of the Contracts within the BusinessContracts.Add parameter weird.

Would you do this??:
BusinessContacts.Add(new Contact{Customer = customer, Type = ContactTypes.Mobile, Description = "555-72 44 55"});

I'm guessing not but I'm not sure.
Left by Gary B on Jun 30, 2008 11:05 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
@Gary: In this case I would implement an AddBusinessContact method in the customer class. Inside this method I handle the details like assigning the backwards link from the contact to the customer. It's way better having methods on the parent object which add or remove items from its collections than directly manipulating the collections of the parent. In this regard my sample used in the post is a little bit sloppy...
Left by Gabriel Schenker on Jun 30, 2008 4:52 PM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Thanks for your insight Gabriel! I truly appreciate it.

So you don't have a problem with domain entities creating other entities. I would guess your entities would depend on some kind of domain factory?

I like this idea but I have seen some suggest that domain entities should not have a dependency on a domain factory and I've also seen some people suggest creating a custom collection to manage the items.

I think I like your idea better but I'm just wondering what your thoughts are on this alternative implementation.
Left by Gary B on Jul 01, 2008 3:39 AM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
@Gary:
- when using custom collections you have more work to do with NHibernate... so I normally avoid them.
- as Eric Evans writes in his DDD book one should use a repository per aggregate root to fetch and modify data. And if it is a complex aggregate which needs a lot of setup logic to use a factory. But if no complex setup logic is needed then there is no need for an extra factory....
- an aggregate (root entity) does never have a dependency on its factory. Rather does e.g. a application service use a factory to create a new aggregate.
Left by Gabriel Schenker on Jul 01, 2008 8:19 PM

# re: Mapping Collections in NHibernate (Part 1)

Requesting Gravatar...
Hi, if you try to implement this post in Oracle with NHibernate, you have to add a trigger to the children table if you want to implement id for those records.
Left by Marcelo Rojas H. on Dec 01, 2008 3:54 AM

Your comment:

 (will show your gravatar)
 
Please add 5 and 8 and type the answer here: