Introduction to NHibernate, Part 3

 

 

Last time we looked at mapping an entity. This time we will look at mapping the entity’s relationships.

Mapping Relationships

 The collection types
that NHibernate understands are: <set>, <list>, <map>,
<bag>, <array> and <primitive-array>

 

<set> is the most commonly used solution as it
represents what most people understand a child to be: an unordered collection
with no duplicates. A <bag> is an unordered collection that allows
duplicates. All other types are ordered, that is they require an index column
in the Db representation. A <map> is indexed either by any NHibernate
type or by an entity. A list or array is indexed by a sequential integer value.

 

.NET does not come with a set type. So, NHibernate uses a
library called Iesi.Collections which includes the interface ISet and many
implementations (like HashedSet). NHibernate maps the bag type onto IList.

 

The collections have differing performance characteristics.
An indexed collection has the best update performance, because they are found
by the foreign key and index. Sets find rows by the foreign key and element values,
which is less performant, especially if they include large text fields. Sets
also suffer in many-to-many associations because they do a delete and insert
not an update. Bags are the worst-case for performance because they have no
index and may contain duplicate elements. Arrays cannot be lazy loaded. For
this reason the optimal choices are list, map, and set. NHibernate best
practice is to default to sets to begin with.

 

The attributes of the collection type are important.
Everything needs the <name> attribute that corresponds to the property on
the class. If you want to use lazy loading of your child collection then you
need to set <lazy=”true”> as it defaults to false. If you want
to cascade updates and deletes then you need to use the <cascade>
attribute to control this.

 

The <inverse> attribute is needed if your
relationship is bi-directional. When we add a new child to a collection
NHibernate adds an insert statement to add the new child row. But then it would
issue an update statement, to update the foreign key on the child row, to be
the primary key of the parent. This is not just inefficient, it may also be
violate constraints which do not allow the foreign key to be null. To correct
this we need to tell NHibernate that the child has a key we need to get from
its parent. We do that by flagging the child as
<inverse=”true”>. Then we only generate the insert statement.

 

We can also set a <fetch> strategy to use an outer-join when retrieving
children. Usually we rely on setting the fetch strategy via the HQL or
ICriteria instead.

 

For any collection we need to add a <key/> child
element. The key is the foreign key column on the child that maps to the
primary key.

 

If the collection is ordered then we need to add an
<index> column. The index column includes the column name and the type of
the column. If the index is an entity we need to use <index-many-to-many>.

We can map to elements instead of entities if we want to
persist a collection of value types instead of entities.

 

On the inverse side of a <one-to-many> relationship we
need a <many-to-one> relationship. The <many-to-one> element
identifies the <name> of the property that represents the parent and the
<column> on the table row which provides the foreign key.

Our example is straightforward; we want a one-to-many
association between company and customer that is bi-directional.

 

<class name=”Company” table=”Company”>

    <id name=”Id”
type=”Int32″ unsaved-value=”0″>

      <generator class=”identity”/>

    </id>

    <version name=”Version”/>

    <property name=”Name”/>

    <set name=”Customers”
inverse=”true” lazy=”true” >

      <key column=”Id”/>

      <one-to-many class=”Customer”/>

    </set>

 </class>

  <class name=”Customer”
table=”Customer”>

    <id name=”Id”
type=”Int32″ unsaved-value=”0″>

      <generator class=”identity”/>

    </id>

    <version name=”Version”/>

    <property name=”DateOfBirth”/>

    <property name=”FirstName”/>

    <property name=”Surname”/>

    <many-to-one name=”Company”
column=”CompanyId” cascade=”all-delete-orphan”/>

  </class>

 

 And the classes that correspond to those collections are as follows.

 

   public class Company
    {
        #region Fields
        private ISet<Customer> customers = new HashedSet<Customer>();
        #endregion
        
        #region Properties
        public int Id { get; set; }
        public string Name { get; set; }
        public ISet<Customer> Customers
        {
            get { return customers; }
            set
            {
                customers = value;
            }
        }
        public int Version { get; set; }
        #endregion
          
    }

 

    public class Customer
    {
        public Company Company { get; set; }
        public DateTime? DateOfBirth { get; set; }
        public string FirstName {get;set;}
        public int Id { get; set; }
        public string Surname {get;set;}
        public int Version { get; set; }
    }

 

 

Not how we use ISet for our collection type.  One mistake many developers beginning work with NHibernate make is to use an IList when they want an ISet. This is because they are used to using IList as an all-purpose collection type in .NET. An IList supports an index operator so you can use random access with elements of the collection i.e you can ask for them by position in the list. For a DB to support this it needs to use a column to store the index values, otherwise the order might change. ISet by contrast does not have an index operator and items in the collection have no order.

 

The lack of index operator on an ISet annoys many developers who start working with NHibernate. Often though they were not genuinely using the index, in which case an IList would be find, but just using it to grab the first element in the list. In that case just use the LINQ extension method of First(), but be aware that no order is guaranteed.

 

Only use an IList if you genuinely want a List or Bag semantics.

 

Notice that the type of our collection is an interface. We need to use an interface and not the concrete type to allow NHibernate to  supply us a proxy to a genuine collection at run time. This is essential to support lazy loading. To lazy load a collection we need to supply a proxy for the genuine collection so that when we do want to iterate over the collection the proxy can load our rows on demand. We will return to this in a later post, but this is also why you should use a unit of work pattern with an ORM, because the proxy will try to load your genuine collection from the Db using the same session that was extant when it was created. If that session is no longer available it will throw an error.

 

Next Time  we will look at mapping inheritance.

 

 

About Ian Cooper

Ian Cooper has over 18 years of experience delivering Microsoft platform solutions in government, healthcare, and finance. During that time he has worked for the DTi, Reuters, Sungard, Misys and Beazley delivering everything from bespoke enterpise solutions to 'shrink-wrapped' products to thousands of customers. Ian is a passionate exponent of the benefits of OO and Agile. He is test-infected and contagious. When he is not writing C# code he is also the and founder of the London .NET user group. http://www.dnug.org.uk
This entry was posted in NHibernate, ORM. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://bnzaxevl.com/ Yknoryek

    8wXMuJ

  • RichB

    This should not be an empty element. You need to remove the /

  • http://gregdoesit.com Gergely Orosz

    “The lack of index operator on an ISet annoys many developers who start working with NHibernate.” So true, thats whyI chose to work with bags (even though I didn’t really need that semantics). Thanks for highlighting that out.

  • A.Pedant

    >> .NET does not come with a set type.
    .NET 3.5 introduced HashSet.

    http://msdn.microsoft.com/en-us/library/bb359438.aspx

  • Ian Cooper

    @Sara I’m hoping this will help. Some of this complexity gets hidden by tools like Castle Active Record and Fluent NHibernate and I’ll cover those later, but I think its useful to understand what is happening under the hood before getting those productivity boosts.

  • http://girldeveloper.com Sara Chipps

    Reading your post, it makes me understand that someone needs to do a “beginner’s guide too…” NHibernate seems much harder when you have never done it before.