Angel \”Java\” Lopez on Blog

May 19, 2011

NHibernate 3 (Part 6) One-To-Many with Many-To-One

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

Previous post
Next post

The database is the same from the previous example (the same name and scripts):

But now, I want to have a reference, in the domain, from chapter to book:

public class Chapter
{
    public virtual Guid Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string Notes { get; set; }
	
	// New property
    public virtual Book Book { get; set; } 
}

So, I changed the Chapter mapping to:

<?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" />
    </id>
    <property name="Title" not-null="true" />
    <property name="Notes" />
    <many-to-one name="Book" column="BookId" />
  </class>
</hibernate-mapping>

The new element is many-to-one. The Book mapping has no change:

<?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" />
    </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>

I added the “show_sql” in the configuration file, NHibernate section:

  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <property name="connection.connection_string">Data Source=.\SQLEXPRESS;Initial Catalog=NHibernate3BooksOneToMany;Integrated Security=True</property>
      <property name="proxyfactory.factory_class">
        NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle
      </property>
     <property name="show_sql">true</property>
      <mapping assembly="Books.Console" />
    </session-factory>
  </hibernate-configuration>

This property is very useful when you are learning about NHibernate: it dumps in console the SQL commands that NHibernate executes during its operation. This is the main console program:

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>()
        };
        cookbook.Chapters.Add(new Chapter() { Title = "Models and Mappings" });
        cookbook.Chapters.Add(new Chapter() { Title = "Configuration and Schema" });
        cookbook.Chapters.Add(new Chapter() { Title = "Sessions and Transactions" });
        session.Save(cookbook);
        tx.Commit();
        session.Close();
    }
}
using (ISession session = sessionFactory.OpenSession())
{
    foreach (Book book in session.Query<Book>().Fetch(b => b.Chapters))
        {
            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));
                System.Console.WriteLine(string.Format("From Book {0}", chapter.Book.Title));
            }
        }
}
System.Console.ReadKey();

A new method is invoke: .Fetch. I'll explain it below. Now, run the program, and examine the console output. First:

NHibernate: INSERT INTO Books (Title, Author,
 Id) VALUES (@p0, @p1, @p2);@p0 = 'NHibernate Cookbook' [Type: String (4000)], @p1 = 
'Jason Dentler' [Type: String (4000)], 
@p2 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)]

This is the command that inserts the new book. Then:

NHibernate: INSERT INTO Chapters (Title, Notes, BookId, Id) VALUES (@p0, @p1, @p 
2, @p3);@p0 = 'Models and Mappings' [Type: String (4000)], @p1 = NULL [Type: 
String (4000)], @p2 = NULL [Type: Guid (0)], @p3 = 
15ce4169-08ef-4f75-8493-8fe68da7e918 [Type: Guid (0)] 
NHibernate: INSERT INTO Chapters (Title, Notes, BookId, Id) VALUES (@p0, @p1, @p2, @p3);
@p0 = 'Configuration and Schema' [Type: String (4000)], @p1 = NULL [Type: String (4000)],
 @p2 = NULL [Type: Guid (0)], @p3 = 3edb510d-4cbd-44fd-b4a1-094a258ec07f [Type: Guid (0)] 
NHibernate: INSERT INTO Chapters (Title, Notes, BookId, Id) VALUES (@p0, @p1, @p2,
 @p3);@p0 = 'Sessions and Transactions' [Type: String (4000)], @p1 = NULL [Type: String (4000)], @p2 = NULL [Type: Guid (0)], @p3 = af025d1b-cdc6-4c8f-9014-71c5dee135e1 [Type: Guid (0)]

These commands insert the three chapters. Hey! They ARE NOT setting the BookId (note @p2 = NULL) and no mention of ChapterIndex. Curiously, NHibernate set those columns IN A SECOND PASS:

NHibernate: UPDATE Chapters SET BookId = @p0, ChapterIndex = @p1 WHERE Id = @p2; 
@p0 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)], @p1 = 0 [Type: 
Int32 (0)], @p2 = 15ce4169-08ef-4f75-8493-8fe68da7e918 [Type: Guid (0)] 
NHibernate: UPDATE Chapters SET BookId = @p0, ChapterIndex = @p1 WHERE Id = @p2; 
@p0 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)], @p1 = 1 [Type: 
Int32 (0)], @p2 = 3edb510d-4cbd-44fd-b4a1-094a258ec07f [Type: Guid (0)] 
NHibernate: UPDATE Chapters SET BookId = @p0, ChapterIndex = @p1 WHERE Id = @p2; 
@p0 = 20d05308-aea1-49f2-9b4a-7441e73dab4e [Type: Guid (0)], @p1 = 2 [Type: 
Int32 (0)], @p2 = af025d1b-cdc6-4c8f-9014-71c5dee135e1 [Type: Guid (0)]

This is weird (and it's the reason why we have to define ChapterIndex and BookId as nullable, as I mentioned in the previous example). I would expect NHibernate save in one pass the Book+Chapters. I don't know why it is the “natural” behavior, and is one of the many behaviors we have to learn in order to master NHibernate. Note that usefulness of show_sql: there are other options to log NHibernate (notable, log4net logging), but this is a simple property you can use now in your initial programs. We should improve this behavior in an upcoming post.

This is the select statement:

NHibernate: select book0_.Id as Id0_0_, chapters1_.Id as Id1_1_, book0_.Title as 
Title0_0_, book0_.Author as Author0_0_, chapters1_.Title as Title1_1_, chapters 
1_.Notes as Notes1_1_, chapters1_.BookId as BookId1_1_, chapters1_.BookId as Boo 
kId0__, chapters1_.Id as Id0__, chapters1_.ChapterIndex as ChapterI5_0__ from Bo 
oks book0_ left outer join Chapters chapters1_ on book0_.Id=chapters1_.BookId

Surprise: The select retrieves Books AND Chapters, using an outer join. In this way, NHibernate can fill the chapters of each book. But this behavior is not the “default” one: the chapters are not loaded every time you retrieves a book. The NHibernate default behavior is to load the Chapters list ONLY when you iterate over it. If you don't use the list, the chapters are not loaded. The above statement (and the adding of the join) is a consequence of using the Fetch:

foreach (Book book in session.Query<Book>().Fetch(b => b.Chapters))

That additional method says: “Hey, NHibernate! Go and get the book, but the chapters too!”. You can experiment, and remove the Fetch method. Then, this is the partial output (another data set):

NHibernate: select book0_.Id as Id0_, book0_.Title as Title0_, book0_.Author as 
Author0_ from Books book0_ 
Book NHibernate Cookbook 
NHibernate: SELECT chapters0_.BookId as BookId1_, chapters0_.Id as Id1_, chapter 
s0_.ChapterIndex as ChapterI5_1_, chapters0_.Id as Id1_0_, chapters0_.Title as Title1_0_,
 chapters0_.Notes as Notes1_0_, chapters0_.BookId as BookId1_0_ FROM Chapters
 chapters0_ WHERE chapters0_.BookId=@p0;@p0 = 
cfb043eb-6ffc-4727-a5ad-05afe867ab57 [Type: Guid (0)] 
Chapter 1:Models and Mappings 
From Book NHibernate Cookbook 
Chapter 2:Configuration and Schema 
From Book NHibernate Cookbook 
Chapter 3:Sessions and Transactions 
From Book NHibernate Cookbook

There is a select for the book, and then, FOR EACH BOOK (trust me ;-) there is a new select for its chapter. Again: another “behavior” to have into account in order to have a program that doesn't abuse of database commands. Note that the output:

Book NHibernate Cookbook

PRECEDES the select of the chapters. Until the program iterates the chapter list, NO SELECT is performed. Another point: chapter.Book is not null. Thanks to NHibernate, the reference from Chapter to its parent Book is automatically filled.

So, this is the reason for the added .Fetch method. We have to explore alternatives to circumvent the pair INSERT-UPDATE for each chapter, topic for the next post.

As usual, the code is at my AjCodeKata Code Project, under trunk/NHibernate/BookChapters. You can download a “frozen” version from NHibernate3BookChapters.zip.

Next steps: explore inverse=”true”, other logging options, many-to-many, session cache, etc.

Keep tuned!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

1 Comment »

  1. [...] Previous Post Next Post [...]

    Pingback by NHibernate 3 (Part 7) One-To-Many with Inverse « Angel “Java” Lopez on Blog — May 28, 2011 @ 11:40 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

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 68 other followers

%d bloggers like this: