Archive for category Fluent NHibernate

Part 7: NHibernate and Ninject for ASP.NET MVC

In part 6, I explained how to set up Ninject with ASP.NET MVC. In this part, we’ll add NHibernate to the mix. Specifically, we’re going to set up session-per-request using a Ninject and bind all the necessary NHibernate interfaces.

Of course, for the sake of history, read up on part 1, part 2, part 3, part 4, part 5, and part 6.

If you aren’t familiar with NHibernate in an ASP.NET MVC application, the most common way to manage your sessions is to open one session per web request. Just about everything you need to know about session-per-request is explained in the content and comments of this post on Ayende’s blog, but I’ll summarize for you.

  • While building a session factory may be a big operation, once it’s built, opening a session is lightweight.
  • Opening a session does not open a connection to the database
  • NHibernate has a built in method for doing session-per-request, but Ayende doesn’t use it for simple stuff and neither will we. When your application doesn’t do anything other than session-per-request, it’s just easier to do it this way.
  • Multiple business transactions and therefore multiple sessions in a single web request are usually not necessary, just because of how users tend to interact with the application. Even then, you can usually accomplish the same thing with multiple DB transactions on the same session.

SessionPerConversation

NHibernate Burrow is available to help with complex session management in web apps where session per conversation is used. Basically, this allows you to span your NHibernate sessions across several web requests. Just a quick note: If you disregarded everyone’s advice and used Identity (integer auto-number) ID fields, Burrow won’t work for you. If you want more information, check out the Burrow posts on NHForge. Also,  Jose Romaniello’s uses Conversation per Business Transaction in his NHibernate and WPF series on NHForge.org. It’s definitely worth a read.

OK. Back to session-per-request. I’m taking a slightly different approach than Ayende. Even though opening a session is lightweight, I don’t like the idea of opening a session for requests that may not use NHibernate at all. For example, in an application I’m building at work, only about 7 views out of nearly 50 actually use an NHibernate session. That’s a lot of unused sessions.

First things first, we need to make a Ninject module for all of our NHibernate bindings. Where are we going to put it? We have two options. We could put it in NStackExample.Data with all of our NHibernate mappings and configuration. We could also put it in NStackExample.Web. Like Ayende, we will be storing the NHibernate session in the context of the current web request and relying on our application’s EndRequest event to close the session. Since we’re unfortunately coupled to the web application, we’ll put it in the web project.

  1. In the web project, make a new folder called Code.
  2. Make a class in that folder called NHibernateModule.
  3. NHibernateModule should inherit from Ninject.Core.StandardModule.

The process of configuring NHibernate is a lot of work and only needs to be done once. Since our configuration object also creates the session factory, another potentially heavy operation, we kill two birds with one stone. The binding for our NHibernate configuration looks like this:

    Public Overrides Sub Load()
        Dim Cfg As New NStackExample.Data.Configuration
        Cfg.Configure()

        Bind(Of NStackExample.Data.Configuration).ToConstant(Cfg)
    End Sub
    public override void Load()
    {
        NStackExample.Data.Configuration Cfg = new NStackExample.Data.Configuration()
        Cfg.Configure();

        Bind().ToConstant(Cfg);
    }

ToConstant bindings essentially create singletons, at least within the scope of our Ninject kernel. Unlike true singletons, this isn’t evil because our tests are free to mock, replace, and re-implement them as necessary.

Now that we have NHibernate configured and our session factory built, we need to bind our NHibernate session. The scope of our session is somewhat complex (per-request). We could use the OnePerRequestBehavior of Ninject, but that requires the registration of an IIS HTTP module. Instead, we’ll just bind it to a method and manage it ourselves. This method will create up to one session per request. If a particular request doesn’t require a session, Ninject will never call the method, so an unnecessary session won’t be created. If a particular request asks for a session more than once, perhaps to build more than one DAO, the method will create a single session and use it throughout the web request. Here’s what our module looks like with the binding for our session:

    Friend Const SESSION_KEY As String = "NHibernate.ISession"

    Public Overrides Sub Load()
        Dim Cfg As New Configuration
        Cfg.Configure()

        Bind(Of Configuration).ToConstant(Cfg)
        Bind(Of NHibernate.ISession).ToMethod(AddressOf GetRequestSession)
    End Sub

    Private Function GetRequestSession(ByVal Ctx As IContext) As NHibernate.ISession
        Dim Dict As IDictionary = HttpContext.Current.Items
        Dim Session As NHibernate.ISession
        If Not Dict.Contains(SESSION_KEY) Then
            'Create an NHibernate session for this request
            Session = Ctx.Kernel.Get(Of Configuration)().OpenSession()
            Dict.Add(SESSION_KEY, Session)
        Else
            'Re-use the NHibernate session for this request
            Session = Dict(SESSION_KEY)
        End If
        Return Session
    End Function
        internal const string SESSION_KEY = "NHibernate.ISession";

        public override void Load()
        {
            Configuration Cfg = new Configuration();
            Cfg.Configure();

            Bind<Configuration>().ToConstant(Cfg);
            Bind<NHibernate.ISession>().ToMethod(x => GetRequestSession(x));
        }

        private NHibernate.ISession GetRequestSession(IContext Ctx)
        {
            IDictionary Dict = HttpContext.Current.Items;
            NHibernate.ISession Session;
            if (!Dict.Contains(SESSION_KEY))
            {
                // Create an NHibernate session for this request
                Session = Ctx.Kernel.Get<Configuration>().OpenSession();
                Dict.Add(SESSION_KEY, Session);
            } else {
                // Re-use the NHibernate session for this request
                Session = (NHibernate.ISession) Dict[SESSION_KEY];
            }
            return Session;
        }

All we have left to do is dispose our session at the end of the request. Let’s go back to the Global.asax codebehind.

    Private Sub MvcApplication_EndRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.EndRequest
        If Context.Items.Contains(NHibernateModule.SESSION_KEY) Then
            Dim Session As NHibernate.ISession = Context.Items(NHibernateModule.SESSION_KEY)
            Session.Dispose()
            Context.Items(NHibernateModule.SESSION_KEY) = Nothing
        End If
    End Sub
        public MvcApplication()
        {
            this.EndRequest += MvcApplication_EndRequest;
        }

        private void MvcApplication_EndRequest(object sender, System.EventArgs e)
        {
            if (Context.Items.Contains(NHibernateModule.SESSION_KEY))
            {
                NHibernate.ISession Session = (NHibernate.ISession) Context.Items[NHibernateModule.SESSION_KEY];
                Session.Dispose();
                Context.Items[NHibernateModule.SESSION_KEY] = null;
            }
        }

To illustrate how this will work, I’ve made several additions to the code download. I’ve added a BaseController and HomeController so we can begin to run our web application. I’ve also added a IStudentDao and ICourseDao interfaces to the core project and corresponding implementations in the Data project. I’ve bound the DAO interfaces to their corresponding implementations and added debug statements to output exactly what’s happening with our session. Finally, I’ve set up a constructor in HomeController making it dependent on IStudentDao and ICourseDao.

When we run our application, we see from the debug output that the session is created when we create our IStudentDao. The session is reused to create our ICourseDao. This gives us everything we need to create the HomeController. The web request executes. When the request ends, the session is disposed. If you remove one of the Dao dependencies from HomeController, you’ll see that our session is created. It’s not reused because nothing else needs a session. If you remove both of the Dao dependencies from HomeController, you’ll see that our session is never even created. Since we didn’t create a session, we don’t dispose it when the web request ends.

That’s all for part 7. In part 8, we’ll wrap the NHibernate transaction for use in our controllers project and build a real DAO or two.

Get your code here! We have VB.NET and CSharp flavored bits.

Jason

- NHibernating Ninja Wannabe

Fluent NHibernate v1 released

Fluent NHibernate v1 was just released. Congratulations to James Gregory and the rest of the FNH team.

Fluent NHibernate upgrade to v1 RC gotchas

In a project I’m doing at work, I have a rather large table-per-subclass hierarchy. When I upgraded the reference to Fluent NHibernate v1 RC, it immediately broke. This was expected. Many of the mapping method names have been shortened. For example, WithLengthOf is now just Length. ColumnName is now just Column. I think this is great.

One thing I wasn’t expecting was the change in JoinedSubclass. The method is deprecated. It still works –mostly. Since the issues this causes are pretty obscure, I’m trying to get the word out to save someone else the same headaches. Isn’t that what a programming blog is really about? I mean, besides all that other stuff like fame and riches.

There are two issues:

  1. The newly deprecated JoinedSubclass ignores mappings without mapped properties. This was an issue for me since I had two mapped classes that inherited all of their properties from the base class but implemented their own behavior. After the upgrade, neither of these were included in my mapping. To correct this, upgrade to SubclassMap – everywhere (see #2).
  2. You cannot mix JoinedSubclass with the new SubclassMap in the same NHibernate configuration. JoinedSubclass messes up some internal state with SubclassMap. If you use both, you will get an incomplete configuration exception with a null reference exception inside.

#2 was especially bad for me because I assumed the exception was because I was using SubclassMap incorrectly. I rolled back my code twice before deciding just to go for it. When I replaced my last JoinedSubclass it all magically started working.

Bidirectional One-to-many with Orphan Delete

Here’s the full write-up on my issue from part 5. Since we’re saying all orphan children should be deleted, it seems logical that our DB schema won’t allow orphaned children, meaning Child.Parent_id should have a NOT NULL constraint. However, when I add this constraint and try to do a cascading orphan delete, NHibernate yells at me because I’ve set the Child.Parent to null / nothing.

If I remove this constraint (not good DB practice in my opinion), we see that the orphan is deleted without ever updating Child.Parent_id to NULL, so it won’t violate any database constraints, as far as I can tell.

Here’s the code in VB:

Imports FluentNHibernate.Mapping
Imports NUnit.Framework

Public Class Parent
    Inherits Entity

    Private m_Children As ICollection(Of Child) = New HashSet(Of Child)

    Public Overridable Property Children() As ICollection(Of Child)
        Get
            Return m_Children
        End Get
        Protected Set(ByVal value As ICollection(Of Child))
            m_Children = value
        End Set
    End Property

End Class

Public Class Child
    Inherits Entity

    Private m_Parent As Parent

    Public Overridable Property Parent() As Parent
        Get
            Return m_Parent
        End Get
        Set(ByVal value As Parent)
            m_Parent = value
        End Set
    End Property

End Class

Public Class ParentMapping
    Inherits ClassMap(Of Parent)
    Public Sub New()
        Id(Function(x As Parent) x.ID).GeneratedBy.GuidComb()
        HasMany(Function(x As Parent) x.Children) _
            .AsSet() _
            .WithForeignKeyConstraintName("ParentChildren") _
            .Cascade.AllDeleteOrphan() _
            .Inverse()

    End Sub
End Class

Public Class ChildMapping
    Inherits ClassMap(Of Child)
    Public Sub New()
        Id(Function(x As Child) x.ID).GeneratedBy.GuidComb()
        References(Function(x As Child) x.Parent) _
            .Cascade.All() _
            .WithForeignKey("ChildParent") _
            .Not.Nullable()

    End Sub
End Class

<TestFixture()> _
Public Class ParentMappingTests
    Inherits BaseFixture

    <Test()> _
    Public Sub CanCascadeSaveFromParentToChild()
        Dim ID As Guid
        Dim P As Parent
        Dim C As Child
        Using Scope As New SQLiteDatabaseScope(Of ParentMapping)
            Using Session = Scope.OpenSession
                Using Tran = Session.BeginTransaction
                    P = New Parent

                    'Add a child of the parent
                    C = New Child With {.Parent = P}
                    P.Children.Add(C)

                    ID = Session.Save(P)
                    Tran.Commit()
                End Using
                Session.Clear()

                Using Tran = Session.BeginTransaction

                    P = Session.Get(Of Parent)(ID)

                    Assert.IsNotNull(P)
                    Assert.AreEqual(ID, P.ID)

                    Assert.AreEqual(1, P.Children.Count)
                    Assert.AreNotSame(C, P.Children(0))
                    Assert.AreEqual(C, P.Children(0))
                    Assert.AreSame(P.Children(0).Parent, P)

                    Tran.Commit()
                End Using

            End Using
        End Using

    End Sub

    <Test()> _
    Public Sub CanDeleteOrphanFromParentToChildren()
        Dim ID As Guid
        Dim P As Parent
        Dim C As Child
        Using Scope As New SQLiteDatabaseScope(Of ParentMapping)
            Using Session = Scope.OpenSession
                Using Tran = Session.BeginTransaction
                    P = New Parent

                    'Add a child of the parent
                    C = New Child With {.Parent = P}
                    P.Children.Add(C)

                    ID = Session.Save(P)
                    Tran.Commit()
                End Using
                Session.Clear()

                Using Tran = Session.BeginTransaction

                    P = Session.Get(Of Parent)(ID)

                    Assert.IsNotNull(P)
                    Assert.AreEqual(ID, P.ID)

                    Assert.AreEqual(1, P.Children.Count)
                    Assert.AreNotSame(C, P.Children(0))
                    Assert.AreEqual(C, P.Children(0))
                    Assert.AreSame(P.Children(0).Parent, P)

                    Tran.Commit()
                End Using
                Session.Clear()

                'Orphan the child
                C = P.Children(0)
                P.Children.Remove(C)
                C.Parent = Nothing

                Using Tran = Session.BeginTransaction
                    'Orhpaned child should be deleted
                    Session.SaveOrUpdate(P)
                    Tran.Commit()
                End Using
                Session.Clear()

                Using Tran = Session.BeginTransaction
                    P = Session.Get(Of Parent)(ID)

                    Assert.AreEqual(0, P.Children.Count)

                    Tran.Commit()
                End Using

            End Using
        End Using
    End Sub

End Class

Here’s the code in C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using FluentNHibernate.Mapping;
using NUnit.Framework;

namespace NStackExample.Data.Tests
{

    public class Parent : Entity
    {

        private ICollection<Child> m_Children = new HashSet<Child>();

        public virtual ICollection<Child> Children
        {
            get { return m_Children; }
            protected set { m_Children = value; }
        }

    }

    public class Child : Entity
    {

        private Parent m_Parent;

        public virtual Parent Parent
        {
            get { return m_Parent; }
            set { m_Parent = value; }
        }

    }

    public class ParentMapping : ClassMap<Parent>
    {
        public ParentMapping()
        {
            Id((Parent x) => x.ID).GeneratedBy.GuidComb();
            HasMany((Parent x) => x.Children)
                .AsSet()
                .WithForeignKeyConstraintName("ParentChildren")
                .Cascade.AllDeleteOrphan()
                .Inverse();
        }
    }

    public class ChildMapping : ClassMap<Child>
    {
        public ChildMapping()
        {
            Id((Child x) => x.ID).GeneratedBy.GuidComb();
            References((Child x) => x.Parent)
                .Cascade.All()
                .WithForeignKey("ChildParent")
                .Not.Nullable();
        }
    }

    [TestFixture()]
    public class ParentMappingTests
    {

        [Test()]
        public void CanCascadeSaveFromParentToChild()
        {
            Guid ID;
            Parent P;
            Child C;
            using (SQLiteDatabaseScope<ParentMapping> Scope = new SQLiteDatabaseScope<ParentMapping>())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        P = new Parent();

                        //Add a child of the parent
                        C = new Child { Parent = P };
                        P.Children.Add(C);

                        ID = (Guid) Session.Save(P);
                        Tran.Commit();
                    }
                    Session.Clear();

                    using (ITransaction Tran = Session.BeginTransaction())
                    {

                        P = Session.Get<Parent>(ID);

                        Assert.IsNotNull(P);
                        Assert.AreEqual(ID, P.ID);

                        Assert.AreEqual(1, P.Children.Count);
                        Assert.AreNotSame(C, P.Children.First());
                        Assert.AreEqual(C.ID , P.Children.First().ID );
                        Assert.AreSame(P.Children.First().Parent, P);

                        Tran.Commit();

                    }
                }

            }
        }

        [Test()]
        public void CanDeleteOrphanFromParentToChildren()
        {
            Guid ID;
            Parent P;
            Child C;
            using (SQLiteDatabaseScope<ParentMapping> Scope = new SQLiteDatabaseScope<ParentMapping>())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        P = new Parent();

                        //Add a child of the parent
                        C = new Child { Parent = P };
                        P.Children.Add(C);

                        ID = (Guid) Session.Save(P);
                        Tran.Commit();
                    }
                    Session.Clear();

                    using (ITransaction Tran = Session.BeginTransaction())
                    {

                        P = Session.Get<Parent>(ID);

                        Assert.IsNotNull(P);
                        Assert.AreEqual(ID, P.ID);

                        Assert.AreEqual(1, P.Children.Count);
                        Assert.AreNotSame(C, P.Children.First());
                        Assert.AreEqual(C.ID, P.Children.First().ID );
                        Assert.AreSame(P.Children.First().Parent, P);

                        Tran.Commit();
                    }
                    Session.Clear();

                    //Orphan the child
                    C = P.Children.First();
                    P.Children.Remove(C);
                    C.Parent = null;

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        //Orhpaned child should be deleted
                        Session.SaveOrUpdate(P);
                        Tran.Commit();
                    }
                    Session.Clear();

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        P = Session.Get<Parent>(ID);

                        Assert.AreEqual(0, P.Children.Count);

                        Tran.Commit();

                    }
                }
            }
        }

    }

}

Part 5: Fixing the Broken Stuff

This is part 5 of my series on ASP.NET MVC with NHibernate. So far, we concentrated on NHibernate and persistence concerns. In this part, we’re going to correct our model and mappings to pass our tests. This will be the last full-time NHibernate post for a while. The next part will be focused on integrating Ninject, our inversion of control / dependency injection framework, with ASP.NET MVC.

If you’re just joining us, you can still catch up.

First, some trivia. According to Fabio Maulo, the NHibernate logo is probably a sleeping marmot.   

Know what you’re fixing

When correcting bugs, you should correct only bugs. This seems obvious. Yes, we write tests so we can find out what’s broken. The less obvious purpose is to know what’s not broken.

Confession: Sometimes I code first, then test. Sometimes I put on my pants, then my shirt. As long as you leave the house fully dressed, the order isn’t all that important. As long as you write your code and tests every day, the order isn’t all that important.

Now, when you’re on a team working on a project, I assume things *should* work a little different. I wouldn’t know. My project team is just me, and I’ve picked up a lot of bad habits from my team over the years.

Here are the results of the NUnit tests from part 4: 2 passed, 3 failed, 5 threw exceptions. 2 out of 10 is actually pretty good for me. Let’s work through these 8 problems one at a time.

 

Bare-minimum NHibernate debugging

NHibernate makes extensive use of the log4net log framework. It’s quick and painless to expose this log to NUni or most other test runners.

  1. In your data test project, add a reference to log4net.dll
  2. Add an app.config
  3. Add a new class called BaseFixture
  4. Set all of your test fixtures to inherit from the base fixture.

Here’s what your app.config should look like with the log4net configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
  </configSections>
  <log4net>
    <appender name="Debugger" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger - %message%newline"/>
      </layout>
    </appender>
    <logger name="NHibernate.SQL">
      <level value="ALL"/>
      <appender-ref ref="Debugger"/>
    </logger>
  </log4net>
</configuration>

Here’s the code for BaseFixture.

Public MustInherit Class BaseFixture

    Protected Shared ReadOnly Log As log4net.ILog = GetLogger()

    Private Shared Function GetLogger() As log4net.ILog
        log4net.Config.XmlConfigurator.Configure()
        Return log4net.LogManager.GetLogger(GetType(BaseFixture))
    End Function

End Class

We’re calling log4net.Config,XmlConfiguration.Configure() just once. This loads the logging configuration from the app.config, which wires up log4net with Console.Out through the ConsoleAppender. With the example configuration, we’ll get to see the SQL NHibernate is executing.

If you want something a lot more powerful, check out Ayende’s NHProf.

Problem #1

NStackExample.Data.Tests.CourseMappingTests.CanCascadeOrphanDeleteFromCourseToSections:
NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing. Type: NStackExample.Section, Entity: NStackExample.Section
                Dim Course As New Course() With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3}

                Dim Section As New Section() With { _
                        .FacultyName = "FacultyName", _
                        .RoomNumber = "R1", _
                        .SectionNumber = "1"}

                Term.AddSection(Section)
                Course.AddSection(Section)

                Using Tran = Session.BeginTransaction()
                    ID = Session.Save(Course)
                    Session.Save(Section)
                    Tran.Commit() ' <==== Exception here
                End Using
                Session.Clear()

When a transaction is committed, the session is flushed to the database. That just means data changes are written to the database. This exception is telling us we’re trying to save an object, but it references another object that isn’t saved. We can infer that this means cascading is turned off for this relationship. When we go to this particular line in the code, we see that this transaction is committing a save (INSERT) of a new course, and that this course references a new section. If this were a TestCascadeSaveFromParentToChild test, we would adjust our mapping. In this case, we’re testing the delete-orphan functionality, not the cascade of inserts and updates. We’ll explicitly save the section in this transaction as well.

After making the change and re-running our tests, we see that the same test is still failing, although it got further.

                'Test removing
                Course.RemoveSection(Section)
                Using Tran = Session.BeginTransaction()
                    Session.Save(Course)
                    Tran.Commit() ' <==== Exception here
                End Using
                Session.Clear()

Now we’re violating a unique constraint. This is because we’ve called Session.Save(Course) twice. Session.Save is for saving new objects only. Session.SaveOrUpdate or simply Session.Update should be used to save the course. Since neither of those return the identifier, we’ll need to get that from our initial Save. We make those change, recompile, and test.

Next, we get this:

NStackExample.Data.Tests.CourseMappingTests.CanCascadeOrphanDeleteFromCourseToSections:
NHibernate.Exceptions.GenericADOException : could not delete collection: [NStackExample.Course.Sections#912b489a-4d12-4bc9-9d68-9c6b0147b799][SQL: UPDATE "Section" SET Course_id = null WHERE Course_id = @p0]
  ----> System.Data.SQLite.SQLiteException : Abort due to constraint violation
Section.Course_id may not be NULL

This message is telling us that when we disassociated the course from the section, NHibernate tried to set the Section’s Course_id to NULL. This violated a not-null constraint. More importantly, this violated our business rule. The section was orphaned and should have been deleted. To corrected it, we update our mappings. In our course mapping, we’ll add Cascade.AllDeleteOrphan() to the one-to-many sections relationship.

        HasMany(Function(x As Course) x.Sections) _
            .AsSet() _
            .WithForeignKeyConstraintName("CourseSections") _
            .Cascade.AllDeleteOrphan()

After a compile and retest, we get this:

NStackExample.Data.Tests.CourseMappingTests.CanCascadeOrphanDeleteFromCourseToSections:
NHibernate.PropertyValueException : not-null property references a null or transient valueNStackExample.Section.Course

This error is strange. Basically, even though we’re going to delete the section now that it’s orphaned, NHibernate is complaining that we’ve set Section.Course = null / nothing. For now, simply to appease the marmot god, we’ll remove our not null constraint on Section.Course. If you turn on log4net NHibernate.SQL logging, you’ll see that this operation wouldn’t violate the NOT NULL database constraint. The orphaned row is being deleted. We’re only failing an internal NHibernate property check. I’m hoping for a better explanation from Tuna, one of the NHibernate gurus, who’s been extremely helpful with this series.

The second problem is basically a disconnect between relational database concepts and object relations. All one-to-many database relationships are bidirectional. The many-to-one is implied. In an object graph, we can have a reference from a parent to its children but not reference from the child back to the parent, or vice-versa. Object relationships are unidirectional. Even though it would indicate a bug in most circumstances, we still have to tell NHibernate which of our two unidirectional relationships is the “real” one that we want to persist to the database. The default is to use the one-to-many. This means that the relationship that is saved is based on membership in a course’s sections collection. We would rather have the relationship based on the many-to-one relationship: the Section’s Course property. To do this, we specify Inverse() in our mapping for Course.Sections. This tells NHibernate that the “other side” of the bidirectional relationship is the one we want to persist.

Bug solved. Onward! Wait. Compile it and rerun your tests. You may have unknowingly fixed other problems.

Problem #2

NStackExample.Data.Tests.CourseMappingTests.CanCascadeSaveFromCourseToSections:
  Expected: <nstackexample.section>
  But was:  <nstackexample.section>

This is another misleading issue. Our test is checking the equality of two sections.

Q: How did we define the equality of a section?

A: We didn’t, so Object.Equals is just looking to see if these two happen to be the same instance. Since one is rehydrated from the database, they aren’t. We’ll have to define our own equality check.

Q: How should we define equality?

A: If two instances represent the same section, they are equal.  Wait. Why are we just talking about sections? Let’s expand that to cover all entities.

Q: Where can we put this rule?

A: We should override Equals In our base Entity class, so all entities can use it.

Q: How do we know if two instances represent the same entity?

A: The ID fields will be equal.

Q: What about when we haven’t persisted the object and don’t have an ID yet?

A: We’ll assume they’re not equal. If a specific class needs something more accurate, it can override Equals again.

Here’s the code:

    Public Overrides Function Equals(ByVal obj As Object) As Boolean
        Dim other As Entity = TryCast(obj, Entity)
        If other Is Nothing Then Return False
        Return ID.Equals(other.ID) AndAlso Not ID.Equals(Guid.Empty)
    End Function

Let’s recompile and test again. Look at that! We have 6 out of 10 tests passing now.

Problem #3

NStackExample.Data.Tests.SectionMappingTests.CanCascadeSaveFromSectionToStudentSections:
NHibernate.PropertyValueException : not-null property references a null or transient valueNStackExample.Student.MiddleName

This particular error can be fixed in two ways. We have defined our Student mapping to not allow null middle names. Our test of the Sections cascade is failing because it doesn’t set a value in middle name. We can either change our test to put something, even an empty string in middle name, or we can change our mapping to allow nulls. I choose option #1. Changing our mapping to allow nulls could lead to NullReferenceExceptions. Let’s set MiddleName = String.Empty around line 83. After a compile and test, we get this error.

NStackExample.Data.Tests.SectionMappingTests.CanCascadeSaveFromSectionToStudentSections:
NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing. Type: NStackExample.StudentSection, Entity: NStackExample.StudentSection

This error is saying that our cascade is failing. Why? Because we didn’t actually specify cascade on one of the one-to-many relationships pointing to  StudentSection. Since we know both Sections and Students should cascade to StudentSection, go add Cascade.All to both. Add Inverse() while you’re there.

Compile and retest. Success.

Problem #4

NStackExample.Data.Tests.StudentMappingTests.CanCascadeSaveFromStudentToStudentSection:
NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing. Type: NStackExample.Student, Entity: NStackExample.Student

This one is a bug in our test. If you look at what we’re testing and what we’re actually saving, you’ll realize that we should be saving Student, not Section. Fix it and try again. Now we have the same MiddleName bug we had in problem #3. Fix it as well. Test again. Now we get a NullReferenceException. Why?

If you look at our test of the Student mapping, you’ll see that we’re not checking the correct results. This was most likely a sloppy cut-and-paste job in the middle of a conference call or some other distracting scenario. Swap in the correct expected results:

                'Check the results
                Using Tran = Session.BeginTransaction
                    Student = Session.Get(Of Student)(ID)

                    Assert.AreEqual(1, Student.StudentSections.Count)
                    Assert.AreEqual(Student.StudentSections(0), StudentSection)

                    Tran.Commit()
                End Using

It works!

Problem #5

NStackExample.Data.Tests.TermMappingTests.CanCascadeSaveFromTermToSections:
NHibernate.TransientObjectException : object references an unsaved transient instance - save the transient instance before flushing. Type: NStackExample.Section, Entity: NStackExample.Section

This is the same as problem #3. Our cascade from term is not cascading the save down to the section. Go add Cascade,All()and Inverse() to Term.Sections.

Problem #6

NStackExample.Data.Tests.TermMappingTests.CanSaveAndLoadTerm:
  Expected: "Fall 2009"
  But was:  null

In this test, we see that we were expecting a value in the Name property of Term, but we got null / nothing. Whenever you see this, you should first check your mapping. In this case, you’ll quickly discover that we didn’t map that property. Go map it. Next, you’ll discover a bug in our tests. We’re comparing the wrong date. EndDate should be compared with December 1st, 2009.

It works!

That really wasn’t so terrible. It probably took more effort to read this post than it did to correct those bugs.

Oh yeah, and get some source control.

Before I post the source code, I’ll be updating to Fluent NHibernate v1.0 RC and fixing some of the typos and reference problems you’ve commented about. With any luck, the corrected source code for this part, along with the next part will be out before the weekend is over.

Edit: Download the entire solution in VB or C#. I’ve upgraded to Fluent NHibernate v1 RC and updated most of the other assemblies.

Jason

- Glad to be moving on to Ninject soon.

Double many-to-one?

I’m working on a mapping for a project at work. The entity names have been changed to protect the innocent.

ClassDiagram1

In the application I’m working on, the model is more like glasses with a left and right lens, but I’ve simplified it.

Public Class Monocle
    Inherits Entity

    Private m_LeftLens As Lens

    Public Overridable Property Lens() As Lens
        Get
            Return m_LeftLens
        End Get
        Set(ByVal value As Lens)
            m_LeftLens = value
        End Set
    End Property

End Class

Public Class Lens
    Inherits Entity

    Private m_Glasses As Monocle

    Public Overridable Property Monocle() As Monocle
        Get
            Return m_Glasses
        End Get
        Set(ByVal value As Monocle)
            m_Glasses = value
        End Set
    End Property

End Class

Public Class MonocleMapping
    Inherits ClassMap(Of Monocle)
    Public Sub New()
        Id(Function(x As Monocle) x.ID).GeneratedBy.GuidComb()
        References(Function(x As Monocle) x.Lens) _
            .Cascade.All() _
            .WithForeignKey("MonocleLens")

    End Sub
End Class

Public Class LensMapping
    Inherits ClassMap(Of Lens)
    Public Sub New()
        Id(Function(x As Lens) x.ID).GeneratedBy.GuidComb()
        References(Function(x As Lens) x.Monocle) _
            .Cascade.All() _
            .WithForeignKey("LensMonocle")
    End Sub
End Class

<TestFixture()> _
Public Class GlassesMappingTests

    <Test()> _
    Public Sub CanCascadeSaveFromMonocleToLens()
        Dim ID As Guid
        Dim Monocle As Monocle
        Dim Lens As Lens
        Using Scope As New SQLiteDatabaseScope(Of MonocleMapping)
            Using Session = Scope.OpenSession
                Using Tran = Session.BeginTransaction
                    Monocle = New Monocle

                    Lens = New Lens With {.Monocle = Monocle}
                    Monocle.Lens = Lens

                    'These are just idiot checks...
                    Monocle.Should.Not.Be.Null()
                    Lens.Should.Not.Be.Null()
                    Monocle.Lens.Should.Be.SameInstanceAs(Lens)
                    Lens.Monocle.Should.Be.SameInstanceAs(Monocle)

                    ID = Session.Save(Monocle)
                    Tran.Commit()
                End Using
            End Using

            Using Session = Scope.OpenSession
                Using Tran = Session.BeginTransaction

                    Monocle = Session.Get(Of Monocle)(ID)

                    Monocle.Should.Not.Be.Null()
                    Monocle.ID.Should.Be.EqualTo(ID)

                    Monocle.Lens.Should.Not.Be.Null()
                    Monocle.Lens.Should.Not.Be.SameInstanceAs(Lens)
                    Monocle.Lens.Should.Be.EqualTo(Lens)
                    Monocle.Lens.Monocle.Should.Be.SameInstanceAs(Monocle)

                    Tran.Commit()
                End Using

            End Using
        End Using

    End Sub

End Class
using NUnit.Framework;
using FluentNHibernate.Mapping;

public class Monocle : Entity
{

    private Lens m_Lens;

    public virtual Lens Lens {
        get { return m_Lens; }
        set { m_Lens = value; }
    }

}

public class Lens : Entity
{

    private Monocle m_Glasses;

    public virtual Monocle Monocle {
        get { return m_Glasses; }
        set { m_Glasses = value; }
    }

}

public class MonocleMapping : ClassMap<Monocle>
{
    public MonocleMapping()
    {
        Id((Monocle x) => x.ID).GeneratedBy.GuidComb();

        References((Monocle x) => x.Lens).Cascade.All().WithForeignKey("MonocleLens");
    }
}

public class LensMapping : ClassMap<Lens>
{
    public LensMapping()
    {
        Id((Lens x) => x.ID).GeneratedBy.GuidComb();
        References((Lens x) => x.Monocle).Cascade.All().WithForeignKey("LensMonocle");
    }
}

[TestFixture()]
public class GlassesMappingTests
{

    [Test()]
    public void CanCascadeSaveFromGlassesToLense()
    {
        Guid ID;
        Monocle Monocle;
        Lens Lens;
        using (SQLiteDatabaseScope<MonocleMapping> Scope = new SQLiteDatabaseScope<MonocleMapping>()) {
            using (Session == Scope.OpenSession()) {
                using (Tran == Session.BeginTransaction()) {
                    Monocle = new Monocle();

                    Lens = new Lens { Monocle = Monocle };
                    Monocle.Lens = Lens;

                    //These are just idiot checks...
                    Monocle.Should.Not.Be.Null();
                    Lens.Should.Not.Be.Null();
                    Monocle.Lens.Should.Be.SameInstanceAs(Lens);
                    Lens.Monocle.Should.Be.SameInstanceAs(Monocle);

                    ID = Session.Save(Monocle);
                    Tran.Commit();
                }
            }

            using (Session == Scope.OpenSession) {
                using (Tran == Session.BeginTransaction) {

                    Monocle = Session.Get<Monocle>(ID);

                    Monocle.Should.Not.Be.Null();
                    Monocle.ID.Should.Be.EqualTo(ID);

                    Monocle.Lens.Should.Not.Be.Null();
                    Monocle.Lens.Should.Not.Be.SameInstanceAs(Lens);
                    Monocle.Lens.Should.Be.EqualTo(Lens);
                    Monocle.Lens.Monocle.Should.Be.SameInstanceAs(Monocle);

                    Tran.Commit();

                }
            }

        }
    }

}

Here’s the resulting SQL.

drop table if exists "Monocle"
drop table if exists "Lens"
create table "Monocle" (ID UNIQUEIDENTIFIER not null, Lens_id UNIQUEIDENTIFIER, primary key (ID))
create table "Lens" (ID UNIQUEIDENTIFIER not null, Monocle_id UNIQUEIDENTIFIER, primary key (ID))
INSERT INTO "Lens" (Monocle_id, ID) VALUES (@p0, @p1); @p0 = '', @p1 = 'f6f37089-a66b-4007-b732-9c6b008d1448'
INSERT INTO "Monocle" (Lens_id, ID) VALUES (@p0, @p1); @p0 = 'f6f37089-a66b-4007-b732-9c6b008d1448', @p1 = 'bb2879f6-d27a-4eb0-8bf0-9c6b008d143b'
UPDATE "Lens" SET Monocle_id = @p0 WHERE ID = @p1; @p0 = 'bb2879f6-d27a-4eb0-8bf0-9c6b008d143b', @p1 = 'f6f37089-a66b-4007-b732-9c6b008d1448'
SELECT monocle0_.ID as ID0_0_, monocle0_.Lens_id as Lens2_0_0_ FROM "Monocle" monocle0_ WHERE monocle0_.ID=@p0; @p0 = 'bb2879f6-d27a-4eb0-8bf0-9c6b008d143b'
SELECT lens0_.ID as ID1_0_, lens0_.Monocle_id as Monocle2_1_0_ FROM "Lens" lens0_ WHERE lens0_.ID=@p0; @p0 = 'f6f37089-a66b-4007-b732-9c6b008d1448'

Of course, I think that making this a double many-to-one relationship is probably wrong, but it gets me close to a working solution.

This works, except for the unnecessary UPDATE “Lens” statement. In my mind, since I’m using the GuidComb generator, NHibernate should specify the Monicle_id in the INSERT “Lens” statement. Instead, it’s inserting NULL, then updating it later. This prevents me from putting a NOT NULL constraint on Lens.Monicle_id. It’s been suggested that I have superfluous update, and I agree. However, the prescribed inverse=”true” won’t work. I don’t have a one-to-many relationship to put it on.

I’ve tried using HasOne instead of References to create a bidirectional one-to-one relationship. It didn’t work at all. My database schema didn’t even have FK fields.

Am I missing something or is this double many-to-one as good as it’s going to get?

NHibernate unit testing with SQLite in-memory DB

While doing research for my next post, I ran across a recurring problem. Everyone wanted to use an in-memory SQLite database for their NHibernate unit tests.

Evidently, there’s no way to get back to the in-memory database in it’s current state after all of the connections to that database have been closed. When you open another connection, you get a fresh blank database.

Here’s the solutions I found while searching around.

  • Ayende just uses a single NHibernate session, which usually only uses a single SQLite DB connection. In fact, he just uses one session per test fixture. I’m not that smart and I don’t know enough about the internals of NHibernate to be confident that I’m really testing what I think I’m testing. Using a fresh session and database for each test would make me a lot more confident.
  • Krzysztof Kozmic has resorted to using a SQLite database stored on disk. While it may be faster than blowing away and recreating a SQL Express database dozens of times, it’s still slower than using an in-memory database. Variations of this idea include storing the database on a MS-DOS 5.0-style RAM disk drive to recover some of the speed lost by using file I/O. Genius! Except, I’m not going to reconfigure my PC for a unit test. Sorry, it’s just not portable enough. That’s why the idea was rated down on Stack Overflow.
  • Someone (gotta love the anonymous bloggers!) suggested building a custom NHibernate DriverConnectionProvider and overriding the GetConnection and CloseConnection functions so that once opened, the connection would never be closed. This seems a bit extreme to me, and doesn’t provide a mechanism to get a fresh database when we need one.

Using the DriverConnectionProvider idea of maintaining a single connection, but with a controlled scope, I came up with this:

Imports FluentNHibernate.Cfg
Imports FluentNHibernate.Cfg.Db
Imports FluentNHibernate.Mapping
Imports NHibernate
Imports NHibernate.Tool.hbm2ddl
Imports System.Data.SQLite

Public Class SQLiteDatabaseScope(Of TClassFromMappingAssembly)
    Implements IDisposable

    Private Const CONNECTION_STRING As String = "Data Source=:memory:;Version=3;New=True;"

    Public Sub New()
        BuildConfiguration()
    End Sub

    Private m_Connection As SQLiteConnection
    Private m_SessionFactory As ISessionFactory

    Private Sub BuildConfiguration()
        m_SessionFactory = Fluently.Configure() _
                            .Database(GetDBConfig()) _
                            .Mappings(AddressOf GetMappings) _
                            .ExposeConfiguration(AddressOf BuildSchema) _
                            .BuildSessionFactory()
    End Sub

    Private Function GetDBConfig() As FluentNHibernate.Cfg.Db.IPersistenceConfigurer
        Return SQLiteConfiguration.Standard _
                    .ConnectionString(Function(cs As ConnectionStringBuilder) cs.Is(CONNECTION_STRING))
    End Function

    Private Sub GetMappings(ByVal x As MappingConfiguration)
        x.FluentMappings _
            .AddFromAssemblyOf(Of TClassFromMappingAssembly)() _
            .ExportTo(".")
    End Sub

    Private Sub BuildSchema(ByVal Cfg As NHibernate.Cfg.Configuration)
        Dim SE As New SchemaExport(Cfg)
        SE.Execute(False, True, False, GetConnection, Console.Out)
    End Sub

    Private Function GetConnection() As System.Data.SQLite.SQLiteConnection
        If m_Connection Is Nothing Then
            m_Connection = New SQLiteConnection(CONNECTION_STRING)
            m_Connection.Open()
        End If
        Return m_Connection
    End Function

    Public Function OpenSession() As ISession
        Return m_SessionFactory.OpenSession(GetConnection)
    End Function

    Private disposedValue As Boolean = False        ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: free other state (managed objects).
                If m_Connection IsNot Nothing Then m_Connection.Close()
                m_Connection = Nothing
            End If

            ' TODO: free your own state (unmanaged objects).
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

It’s the same idea as the DriverConnectionProvider, but with a different scope. We create and open a connection and maintain it for the lifespan of the SQLiteDatabaseScope object. So far, I haven’t found an instance where NHibernate closes the connection.

Each instance of SQLiteDatabaseScope uses a different in-memory database, thanks to the New=True property in the connection string. You should be able to run your tests in parallel. You can’t do that with any of the other solutions.

The generic type parameter should be set to one of your fluent mapping classes. It’s used to tell Fluent NHibernate which assembly to scan for fluent mappings.

It’s extremely easy to use.

Imports NUnit.Framework

<TestFixture> _
Public Class SomeNHibernateTestFixture

    <Test>
    Public Sub SomeNHibernateTest()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using SessionOne = Scope.OpenSession
                ' Do some stuff here...
            End Using

            Using SessionTwo = Scope.OpenSession
                ' Do some more stuff using the same in-memory SQLite DB
            End Using
        End Using
    End Sub

    <Test>
    Public Sub AnotherNHibernateTest()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using SessionThree = Scope.OpenSession
                ' Do some stuff here to a new, freshly-built database...
	    End Using
        End Using
    End Sub

End Class

Jason

- Concerned the problem is not really that simple.

 

Download the SQLiteDatabaseScope code in Visual Basic or C#. GoDaddy doesn’t like you to download .vb or .cs files, so don’t forget to take off the .txt extension before you add it to your project.

Tags: ,

How-To: Using the N* Stack, part 3

This is the third installment in my series. In part 1, we downloaded our libraries and set up our solution. In part 2, we built our model. In this part, we’ll configure NHibernate and set up our database mappings. We’ll also set up our database schema.

Java – A language of XML files loosely coupled by code.

Before we can talk about Fluent NHibernate, you need to know a little bit about setting up mappings in plain old NHibernate. In a typical NHibernate setup, you’ll have a bunch of mapping files like this:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    <class name="NStackExample.Address, NStackExample.Core" table="Address">
        <composite-id>
            <key-many-to-one name="Person" class="NStackExample.Person, NStackExample.Core" column="ID" />
            <key-property name="Type" type="Int32" />
        </composite-id>
        <property name="City" type="String" length="255" />
        <property name="Lines" type="String" length="255" />
        <property name="State" type="String" length="2" />
        <property name="Zip" type="String" length="10" />
    </class>
</hibernate-mapping>

You’ll have one of those for each of your entities. It’s left over from Java’s Hibernate project, and in my opinion, It’s a royal pain, complete with ruby scepter. Lucky for you, there’s a better way™.

A Better Way™: Fluent Mappings

With Fluent NHibernate, the mapping file above can be expressed using this class instead:

Imports FluentNHibernate.Mapping

Public Class AddressMapping
    Inherits ClassMap(Of Address)

    Public Sub New()
        UseCompositeId _
            .WithKeyReference(Function(x As Address) x.Person) _
            .WithKeyProperty(Function(x As Address) x.Type)
        Map(Function(x As Address) x.Lines).WithLengthOf(255)
        Map(Function(x As Address) x.City).WithLengthOf(255)
        Map(Function(x As Address) x.State).WithLengthOf(2)
        Map(Function(x As Address) x.Zip).WithLengthOf(5)

    End Sub

End Class
using FluentNHibernate.Mapping;

namespace NStackExample.Data
{

    public class AddressMapping : ClassMap<Address>
    {

        public AddressMapping()
        {
            UseCompositeId()
                .WithKeyReference(x => x.Person)
                .WithKeyProperty(x => x.Type);
            Map(x => x.Lines).WithLengthOf(255);
            Map(x => x.City).WithLengthOf(255);
            Map(x => x.State).WithLengthOf(2);
            Map(x => x.Zip).WithLengthOf(5);
        }

    }
}

It may look even more complicated than the XML mapping, but with Intellisense, it’s a breeze. Plus, there are no magic strings to worry about. When you change a property name using a refactor tool, your mapping won’t be left out of sync.

Now that you have the basic idea, let’s get back on track.

Where?

Since the database connection, NHibernate configuration, entity mappings, and DAO implementations are really just implementation details of our chosen ORM, they should go in a separate assembly.

  1. Make a new Class Library project called NStackExample.Data
  2. In the new Data project, add references to your core project, NHibernate.dll and FluentNHibernate.dll
  3. Add a reference to System.Configuration.dll so we can easily retrieve some application settings later.
  4. Also, the web project needs a reference to the data project.

Now, let’s make our mappings.

Imports FluentNHibernate.Mapping

Public Class CourseMapping
    Inherits ClassMap(Of Course)

    Public Sub New()
        Id(Function(x As Course) x.ID).GeneratedBy.GuidComb()
        Map(Function(x As Course) x.Subject).Not.Nullable.WithLengthOf(4).UniqueKey("CourseNaturalKey")
        Map(Function(x As Course) x.CourseNumber).Not.Nullable.WithLengthOf(4).UniqueKey("CourseNaturalKey")
        Map(Function(x As Course) x.Title).Not.Nullable.WithLengthOf(255)
        Map(Function(x As Course) x.Description).Not.Nullable.WithLengthOf(1024)
        Map(Function(x As Course) x.Hours).Not.Nullable()

        HasMany(Function(x As Course) x.Sections) _
            .AsSet() _
            .WithForeignKeyConstraintName("CourseSections")

    End Sub

End Class
using NStackExample;
using FluentNHibernate.Mapping;

namespace NStackExample.Data
{
    public class CourseMapping : ClassMap<Course>
    {
        public CourseMapping()
        {
            Id(x => x.ID).GeneratedBy.GuidComb();
            Map(x => x.CourseNumber)
                .Not.Nullable()
                .WithLengthOf(4)
                .UniqueKey("CourseNaturalKey");

            Map(x => x.Subject)
                .Not.Nullable()
                .WithLengthOf(4)
                .UniqueKey("CourseNaturalKey");

            Map(x => x.Title)
                .Not.Nullable()
                .WithLengthOf(255);

            Map(x => x.Description)
                .Not.Nullable()
                .WithLengthOf(1024);

            Map(x => x.Hours)
                .Not.Nullable();

            HasMany(x => x.Sections)
                .AsSet()
                .WithForeignKeyConstraintName("CourseSections");

        }

    }
}

Most of this is self-explanatory and works exactly like you would expect.

Our mapping class inherits from ClassMap(Of Course). ClassMap is the specific type that Fluent NHibernate searches for when looking for mappings. In this case, it signifies that this class provides the mapping for our Course entity. In the constructor, we define our specific mapping for each property.

  • Id sets up the persistent object identifier (POID). This is basically the primary key for the table. If you have more than one property in the primary key, as in the case of natural keys, go with UseCompositeId like in the address example above. Using multi-part keys isn’t really suggested and to my knowledge, isn’t fully supported by Fluent NHibernate.
  • GeneratedBy specifies the POID generator. How will you assign your keys? In my case, I use GuidComb. I get all of the benefits of guid identifiers, but I don’t fragment my database index nearly as much. You can read up on it more in Davy Brion‘s post on the NHForge blog.
  • Map simply maps a property to a database column. You can specify Not.Nullable and WithLengthOf as necessary.
  • UniqueKey specifies a unique index on the column. If you specify the same name on several columns, all of those columns will be part of the same unique index. In this example, we are forcing our natural key to be unique. Each combination of subject and course number must be unique. There can only be one ENGL 1301 course. Thank goodness.
  • HasMany defines a one-to-many relationship. You can specify the exact behavior of the collection. You have several options here, but the two types I use almost exclusively are Set and Bag.
    • AsSet doesn’t allow duplicate items.
    • With AsBag, duplicates are allowed.

By default, all relationships are lazy-loaded. This means that when you fetch a course from the database, the associated sections aren’t fetched right away. It works just like you would expect: They aren’t fetched until you access the Sections property. If you never access the Sections property, those sections are never fetched from the database, which can greatly improve performance. This is all made possible with proxies, but that’s another series of posts.

Now let’s map the sections:

Imports FluentNHibernate.Mapping

Public Class SectionMapping
    Inherits ClassMap(Of Section)

    Public Sub New()
        Id(Function(x As Section) x.ID).GeneratedBy.GuidComb()

        Map(Function(x As Section) x.FacultyName).WithLengthOf(255)
        Map(Function(x As Section) x.RoomNumber).WithLengthOf(10)
        Map(Function(x As Section) x.SectionNumber) _
            .WithLengthOf(4) _
            .Not.Nullable() _
            .UniqueKey("SectionNaturalKey")

        References(Function(x As Section) x.Course) _
            .Not.Nullable() _
            .UniqueKey("SectionNaturalKey")

        References(Function(x As Section) x.Term) _
            .Not.Nullable() _
            .UniqueKey("SectionNaturalKey")

        HasMany(Function(x As Section) x.StudentSections) _
            .AsSet() _
            .WithForeignKeyConstraintName("SectionStudentSections")

    End Sub
End Class

The References function maps the Many-to-one relationship. Think of it as the other side of our one-to-many relationship. It is the reference from the child – section – back to it’s parent – course.

For homework, finish mapping all of the entities.

I bet you’re thinking this post is getting long considering we haven’t even started building the database. Well don’t worry. NHibernate will do that for us.

8 hours or 8 minutes?

Before I discovered NHibernate, I would spend at least a day setting up my database. It was insane. It drove me insane. I bet it drives you insane. It ends today.

Disclaimer: If you are trying to use an existing shared legacy database, the chances of your existing DB schema working without some tweaking are slim. This post by Fabio Maulo explains your options.

First, let’s configure NHibernate. The Fluent NHibernate Wiki has a great page explaining the fluent configuration of NHibernate.

Imports NHibernate
Imports NHibernate.Tool.hbm2ddl
Imports FluentNHibernate.Cfg
Imports System.Configuration
Imports System.IO

Public Class Configuration

    Private m_SchemaPath As String
    Private m_Factory As ISessionFactory

    Public Function Configure() As Configuration
        m_SchemaPath = ConfigurationManager.AppSettings("NStackExample.Data.Configuration.SchemaPath")
        m_Factory = Fluently.Configure _
            .Database(Db.MsSqlConfiguration.MsSql2005 _
                      .ConnectionString(Function(x As Db.MsSqlConnectionStringBuilder) _
                                            x.FromConnectionStringWithKey("NStackExample.Data.Configuration.DB"))) _
            .Mappings(Function(x As MappingConfiguration) _
                          x.FluentMappings.AddFromAssemblyOf(Of CourseMapping)() _
                          .ExportTo(m_SchemaPath)) _
            .ExposeConfiguration(AddressOf BuildSchema) _
            .BuildSessionFactory()
        Return Me
    End Function

    Private Sub BuildSchema(ByVal Cfg As NHibernate.Cfg.Configuration)
        Dim SchemaExporter As New NHibernate.Tool.hbm2ddl.SchemaExport(Cfg)
        SchemaExporter.SetOutputFile(Path.Combine(m_SchemaPath, "schema.sql"))
        SchemaExporter.Create(False, True)
    End Sub

    Public Function OpenSession() As ISession
        If m_Factory Is Nothing Then Configure()
        Return m_Factory.OpenSession
    End Function

End Class
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using System.IO;
using System.Configuration;

namespace NStackExample.Data
{
    public class Configuration
    {

        private ISessionFactory m_Factory;
        private string m_SchemaPath;

        public Configuration Configure()
        {

            m_SchemaPath = ConfigurationManager.AppSettings["NStackExample.Data.Configuration.SchemaPath"];

            m_Factory = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2005
                        .ConnectionString(
                         x => x.FromConnectionStringWithKey("NStackExample.Data.Configuration.Db")))
                .Mappings(x => x.FluentMappings.AddFromAssemblyOf<CourseMapping>()
                                .ExportTo(m_SchemaPath))
                .ExposeConfiguration(BuildSchema)
                .BuildSessionFactory();

            return this;
        }

        private void BuildSchema(NHibernate.Cfg.Configuration cfg)
        {
            SchemaExport SchemaExporter = new SchemaExport(cfg);
            SchemaExporter.SetOutputFile(Path.Combine(m_SchemaPath, "schema.sql"));
            SchemaExporter.Create(true, false);
        }

        public ISession OpenSession()
        {
            if (m_Factory == null) Configure();
            return m_Factory.OpenSession();
        }

    }
}

The configuration falls in to two sections: Database and Mappings. In our case, the database is SQL 2005 and the connection string is read from a connection string element in the web.config. All of the mappings are fluent, not auto-mapped. Notice that we are exporting our mappings to a directory specified in the appsettings section of the web.config. This will convert our fluent mappings to individual hbm.xml files. This is great for debugging the mappings, especially when asking for NHibernate help online.

We have one additional item. We’re using the ExposeConfiguration method to call our BuildSchema function, passing in our complete NHibernate configuration.

In BuildSchema, we use a great hidden tool in NHibernate: the schema export. This amazing class will build your database for you. The create function takes two boolean parameters. The first specifies if the schema should be written out to a ddl file – a database script to build all of the tables, keys, indexes, and relationships in your database. The second boolean parameter specifies if the script should be executed against the specified database.

It’s that easy.

Two warnings:

  1. Executing this script will drop and recreate every table associated with your model. That can be devastating in a production environment.
  2. The script doesn’t start with a a “use [databasename]” statement, so if you’re not careful, when you execute it, you’ll build everything in the master database.

One last note: As with any project, you will have to adapt as you build. These mappings are not exactly what we use in the final build. I can guarantee our model will change significantly. I will take you through those changes as they happen, and explain the reasons behind them.

I’ve decided not to post the complete source code at this stage. Instead, I leave the remaining mappings as an exercise for you, the reader. They will be included in the next source release.

In the next post, I’ll show you how to test your mappings – including querying, reading from and writing to the database.

Jason

- Mapped out. Good night.

P.S. – Special thanks to Tuna, Fabio, and Oren for the feedback, answers to stupid questions, and great advice!