Ian Cooper

Sponsors

The Lounge

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
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.

 

 


Posted Wed, Jan 7 2009 9:14 AM by Ian Cooper
Filed under: ,

[Advertisement]

Comments

Dew Drop - January 7, 2009 | Alvin Ashcraft's Morning Dew wrote Dew Drop - January 7, 2009 | Alvin Ashcraft's Morning Dew
on Wed, Jan 7 2009 10:12 AM

Pingback from  Dew Drop - January 7, 2009 | Alvin Ashcraft's Morning Dew

Arjan`s World » LINKBLOG for January 7, 2009 wrote Arjan`s World &raquo; LINKBLOG for January 7, 2009
on Wed, Jan 7 2009 4:26 PM

Pingback from  Arjan`s World    &raquo; LINKBLOG for January 7, 2009

Sara Chipps wrote re: Introduction to NHibernate, Part 3
on Thu, Jan 8 2009 12:16 AM

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.

Reflective Perspective - Chris Alcock » The Morning Brew #260 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew #260
on Thu, Jan 8 2009 3:51 AM

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

Ian Cooper wrote re: Introduction to NHibernate, Part 3
on Thu, Jan 8 2009 5:54 AM

@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.

A.Pedant wrote re: Introduction to NHibernate, Part 3
on Tue, Jan 13 2009 4:58 PM

>> .NET does not come with a set type.

.NET 3.5 introduced HashSet.

msdn.microsoft.com/.../bb359438.aspx

Ian Cooper [MVP] wrote Introduction to NHibernate, pt. 4
on Wed, Jan 21 2009 5:15 PM

Last time we started looking at the parts of an OO model that need translation to relational form when

Community Blogs wrote Introduction to NHibernate, pt. 4
on Wed, Jan 21 2009 5:47 PM

Last time we started looking at the parts of an OO model that need translation to relational form when

Invalid Argument » Introduction to NHibernate wrote Invalid Argument &raquo; Introduction to NHibernate
on Thu, Jan 22 2009 9:31 PM

Pingback from  Invalid Argument &raquo; Introduction to NHibernate

Gergely Orosz wrote re: Introduction to NHibernate, Part 3
on Wed, Jan 28 2009 6:56 AM

"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.

RichB wrote re: Introduction to NHibernate, Part 3
on Mon, Mar 30 2009 7:57 AM

<set name="Customers" inverse="true" lazy="true" />

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

Yknoryek wrote re: Introduction to NHibernate, Part 3
on Mon, Jul 13 2009 7:26 PM

8wXMuJ

Add a Comment

(required)  
(optional)
(required)  
Remember Me?