Angel \”Java\” Lopez on Blog

May 9, 2011

NHibernate 3 (Part 5) First One-To-Many Mapping

Filed under: .NET, C Sharp, NHibernate — ajlopez @ 9:57 am

Previous post
Next post

This time, I want to map this data model:

Each book can have zero, one or more chapters. This is the class diagram for my domain:

This is the code for Book.cs:

public class Book
{
    public virtual Guid Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Author { get; set; }
    public virtual IList<Chapter> Chapters { get; set; }
}

As usual, the properties are virtual (I should write why, in upcoming post). And note, Chapters is a IList<Chapter>. The IList will be loaded by NHibernate. The Chapter.cs class:

public class Chapter
{
    public virtual Guid Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Notes { get; set; }
}

The book mapping has a new element:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Books"
namespace="Books">
  <class name="Book" table="Books">
    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <property name="Title" not-null="true" />
    <property name="Author" not-null="true"/>
    <list name="Chapters" cascade="all-delete-orphan">
      <key column="BookId"/>
      <index column="ChapterIndex"/>
      <one-to-many class="Chapter"/>
    </list>
  </class>
</hibernate-mapping>

The <list> element points to the Book property Chapters. The elements will be ordered based on the table column ChapterIndex. BookId and ChapterIndex are not properties in Chapter class. They are columns in Chapter table. The Chapter mapping has no reference to Book class:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Books"
namespace="Books">
  <class name="Chapter" table="Chapters">
    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <property name="Title" not-null="true" />
    <property name="Notes" />
  </class>
</hibernate-mapping>

What does the “cascade=all-delete-orphan” attribute mean? You have more info at:

http://www.nhforge.org/doc/nh/en/index.html#collections-onetomany

The programmer can add and remove chapters from/to a book object, and NHibernate will take care of their persistence. If an object is removed from the list via code, NHibernate will delete the removed chapter IF it has not added to OTHER book. It would be an “orphan” chapter.

This is the code to add and retrieve a book with chapters:

ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
using (ISession session = sessionFactory.OpenSession())
{
    using (ITransaction tx = session.BeginTransaction())
    {
        Book cookbook = new Book()
        {
            Title = "NHibernate Cookbook",
            Author = "Jason Dentler",
            Chapters = new List<Chapter>() { 
                new Chapter() { Title = "Models and Mappings" },
                new Chapter() { Title = "Configuration and Schema" },
                new Chapter() { Title = "Sessions and Transactions" }
            }
        };
        session.Save(cookbook);
        foreach (Book book in session.Query<Book>())
        {
            System.Console.WriteLine(string.Format("Book {0}", book.Title));
            int nchapter = 0;
            foreach (Chapter chapter in book.Chapters)
                System.Console.WriteLine(string.Format("Chapter {0}:{1}", ++nchapter, chapter.Title));
        }
        tx.Commit();
        session.Close();
    }
}

I had a problem: initially, I defined ChapterIndex and BookId as not-null in Chapter table. But, notably, NHibernate requires that those fields could accept null values. So, in my first attempts, I got an error when I save a new book. Then, I read the note (from the above link in NHForge documentation):

Very Important Note: If the <key> column of a <one-to-many> association is declared NOT NULL, NHibernate may cause constraint violations when it creates or updates the association. To prevent this problem, you must use a bidirectional association with the many valued end (the set or bag) marked as inverse="true". See the discussion of bidirectional associations later in this chapter.

I changed the columns to nullable, and all worked OK. I’m still puzzle by this requirement: I expected that NHibernate filled the BookId and ChapterIndex DURING the saving of each chapter. But not. It saved the chapter with those fields with null, and THEN, it updated the row.

The code of this post, as usual, is in my AjCodeKatas Google Project, under trunk/NHibernate/BooksOneToMany. You can download a “current frozen” version from NHibernate3BooksOneToMany.zip.

Next steps: explore more one-to-many options, bidirectional mappings, and more.

My sources of information: book: Jason Dentler’s NHibernate 3.0 Cookbook

http://ayende.com/Blog/archive/2006/12/02/nhibernatecascadesthedifferentbetweenallalldeleteorphansandsaveupdate.aspx

http://www.nhforge.org/doc/nh/en/index.html#collections-onetomany

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

3 Comments »

  1. […] Previous post Next post […]

    Pingback by NHibernate 3 (Part 4) Table Per Class « Angel “Java” Lopez on Blog — May 9, 2011 @ 10:01 am

  2. […] Previous post Next post […]

    Pingback by NHibernate 3 (Part 6) One-To-Many with Many-To-One « Angel “Java” Lopez on Blog — May 28, 2011 @ 11:43 am

  3. Hi Angel,

    Good post, thanks. In his book Jason has put some detailed logic on “managing” the addition / removal of items to the classes, which would suggest a AddChapter and RemoveChapter methods in the Book Class. I didn’t see you implement or mention these, any reason you did not do this?

    Regards, Mickey

    Comment by Mickey Puri — May 21, 2012 @ 10:04 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: