Archive for category NUnit

Part 10: Testing and Refactoring

Today’s post will be short. I’m going to cover the basics of testing with Rhino Mocks and do some refactoring in the DAOs.

I’m not an expert. This is just how I do things. If you have a better way, do it your way. Better yet, tell me about it so I can improve the way I work as well.

Testing Terminology

In recent years, testing vocabulary has exploded. There are mocks and stubs and fakes and unit tests and integration tests and acceptance tests and all sorts of jargon. You may be thinking “who cares?” This jargon is only important when code needs to communicate its intent to humans, right?. The compiler doesn’t care what terminology we use. Well, sorry. Code is written for humans, not compilers, so programming jargon is a prerequisite.

Test Doubles

So, let’s go over some common terms. I’m going to lift these definitions straight from Marton Fowler’s Mocks Aren’t Stubs. Test Double is a “generic term for any kind of pretend object used in place of a real object for testing purposes.” That’s pretty straight forward. Test doubles come in four types:

  • Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.
  • Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).
  • Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it ’sent’, or maybe only how many messages it ’sent’.
  • Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

Hi. Still with me? Good.

Mocks are significant. They are part of the “proof” of the test. The other three amount to plumbing. Now, you may be asking your self why we even need test doubles. Why can’t we just run our production code and inspect the output? Fowler’s article has several sections about the differences and pros and cons of classical testing (using objects from the real code) vs. mockist testing (creating doubles for everything except what you’re testing).

Even in classical testing, you sometimes have to swap in a test double for objects that lead to permanent side effects or operate too slowly. In mockist testing, you swap in test doubles for everything that you’re not explicitly testing. Either way, you need to know how to create and use test doubles.

We’re going to use Rhino Mocks, a fluent framework for creating stubs and mocks. I don’t know if it’s the best, but if Ayende wrote it, you can bet it’s pretty darn awesome. Plus, the fluent syntax works OK in VB.NET, which is rare.

Writing the Test

Suppose we had a standard GetByID function on our DAO (because we do) containing some code like this (because it does). How would we test that the function actually did what it claimed?

        If m_Session.Transaction Is Nothing OrElse Not m_Session.Transaction.IsActive Then
            Dim RetVal As TEntity
            Using Tran = m_Session.BeginTransaction
                RetVal = m_Session.Get(Of TEntity)(ID)
                Tran.Commit()
                Return RetVal
            End Using
        Else
            Return m_Session.Get(Of TEntity)(ID)
        End If
            if (null == m_Session.Transaction || !m_Session.Transaction.IsActive)
            {
                TEntity retval;
                using (var Tran = m_Session.BeginTransaction())
                {
                    retval = m_Session.Get<TEntity>(ID);
                    Tran.Commit();
                    return retval;
                }
            }
            else
            {
                return m_Session.Get<TEntity>(ID);
            }

Because I’m not an expert, I won’t try to explain the computer science of testing. I can tell you that if we ignore possible branches inside NHibernate objects, there are two possible paths through our function (the first if we don’t have an existing explicit transaction, and the second if we do), giving us a cyclomatic complexity of 2. This means that we need two unit tests to achieve 100% code coverage. 100% code coverage doesn’t mean perfect code, but it helps.

I prefer the Record / Playback style of testing. In this style, you start by setting up your expectations within a record section – which mock methods will be called, how many times they’ll be called, in what order they’ll be called, and what their return values should be. Then, in the playback section, you perform the actual action. In this case, we’ll create an instance of our DAO and call its GetByID method. Finally, you verify that the expectations of your mock were met, as well as any other assertions you may need to prove.

Edit: The alternative to Record / Playback is Arrange / Act / Assert. If you don’t know the difference, here’s a good article Jose sent me. Rhino Mocks supports both styles. I still prefer Record / Playback, probably just because I’m used to it.

Here’s what a test of GetByID with a pre-existing transaction would look like:

    <Test()> _
    Public Sub GetByIDTest()
        Dim mocks As New MockRepository()
        Dim session As NHibernate.ISession = mocks.StrictMock(Of NHibernate.ISession)()
        Dim transaction As NHibernate.ITransaction = mocks.Stub(Of NHibernate.ITransaction)()
        Dim expected As Student = mocks.Stub(Of Student)()
        Dim actual As Student
        Using mocks.Record()
            Rhino.Mocks.Expect.Call(session.Transaction).Return(transaction).Repeat.Any()
            Rhino.Mocks.Expect.Call(transaction.IsActive).Return(True)
            Rhino.Mocks.Expect.Call(session.Get(Of Student)(Guid.Empty)).Return(expected)
        End Using

        Using mocks.Playback()
            Dim StudentDao As IReadStudent = New StudentDaoImpl(session)
            actual = StudentDao.GetByID(Guid.Empty)
        End Using
        mocks.VerifyAll()
        Assert.IsNotNull(actual, "null entity returned")
        Assert.AreSame(expected, actual, "wrong entity returned")
    End Sub
        [Test]
        public void GetByIDTest()
        {
            MockRepository mocks = new MockRepository();
            NHibernate.ISession session = mocks.StrictMock<NHibernate.ISession>();
            NHibernate.ITransaction transaction = mocks.Stub<NHibernate.ITransaction>();
            Student expected = new Student();
            Student actual;
            using (mocks.Record())
            {
                Rhino.Mocks.Expect.Call(session.Transaction)
                    .Return(transaction)
                    .Repeat.Any();
                Rhino.Mocks.Expect.Call(transaction.IsActive)
                    .Return(true);
                Rhino.Mocks.Expect.Call(session.Get<Student>(Guid.Empty))
                    .Return(expected);
            }
            using (mocks.Playback())
            {
                IReadStudent StudentDao = new StudentDAOImpl(session);
                actual = StudentDao.GetById(Guid.Empty);
            }
            mocks.VerifyAll();
            Assert.IsNotNull(actual);
            Assert.AreSame(expected,actual);
        }

We start by creating a MockRepository. This is the factory for all of our mocks and stubs, controls our record and playback blocks, and verifies that all the mock expectations have been met.

Next, we create a mock of the NHIbernate session and a stub of an NHibernate transaction. We create a mock because we want to make sure our DAO calls m_Session.Get<>. We also create a double for our return value called expected. We’ll compare it to the actual return value.

Now that we have our doubles, we set up our expectations. We are testing the path of the pre-existing transaction. Session.transaction will return our transaction stub. Since this is a mock, not a stub, the default is to assert that it is called exactly once. Since we’re not interested in this part, we specify that it can be called any number of times. We also specify that a call to transaction.IsActive should return true. Finally, we specify that our DAO will call session.Get<> exactly once, and that our mock session should return our expected student.

Next, we start our playback block and perform the action. We create an instance of our DAO, passing in the mock session we wired up in our record block, and we call GetByID.

Finally, we verify that our DAO interacted with the stub as expected. We also assert that the actual instance returned during our test is the same as our expected instance.

This covers the first test. What about the second one? Well, the test would be identical except for your expectations. We would setup our transaction stub so that transaction.IsActive returned false. We would also expect our DAO to call session.BeginTransaction().

Now, I’m lazy and we’ve already done things backwards by writing our code before our tests, so let’s continue being lazy. I don’t want to write two tests for each DAO method just because of some transaction handling code, which by the way, is repeated all over the place. Not good. Let’s refactor things a bit.

Refactored Transaction Handling

In all of the DAO methods, I’ve made the choice to ensure we have an explicit transaction before interacting with the database. In previous versions, each method is nearly identical to the code we tested above above.

In all of this, the only unique code is the call to m_Session.Get(). The rest of the code is just uninteresting transaction handling, and this uninteresting plumbing code is repeated in every method of our DAO. Let’s pull it out in to its own function.

    Protected Function WrapInTransaction(ByVal F As Func(Of TEntity)) As TEntity
        Return WrapInTransaction(Of TEntity)(F)
    End Function

    Protected Function WrapInTransaction(Of TResult)(ByVal F As Func(Of TResult)) As TResult
        If m_Session.Transaction Is Nothing OrElse _
            m_Session.Transaction.IsActive = False Then
            Using Tran = m_Session.BeginTransaction
                Dim RetVal As TResult = F.Invoke()
                Tran.Commit()
                Return RetVal
            End Using
        Else
            Return F.Invoke()
        End If
    End Function
        protected TEntity WrapInTransaction(System.Func<TEntity> F)
        {
            return WrapInTransaction<TEntity>(F);
        }

        protected TResult WrapInTransaction<TResult>(System.Func<TResult> F)
        {
            if (null == m_Session.Transaction || !m_Session.Transaction.IsActive)
            {
                using (NHibernate.ITransaction Tran = m_Session.BeginTransaction())
                {
                    TResult RetVal = F.Invoke();
                    Tran.Commit();
                    return RetVal;
                }
            }
            else
            {
                return F.Invoke();
            }
        }

Now we can pass in the small-but-interesting bit of code as a parameter to the WrapInTransaction method. This lets us simplify our methods down to this:

    Public Function GetByID(ByVal ID As System.Guid) As TEntity Implements IRead(Of TEntity).GetByID
        Return WrapInTransaction(Function() m_Session.Get(Of TEntity)(ID))
    End Function
        public TEntity GetById(System.Guid ID)
        {
            return WrapInTransaction(() => m_Session.Get<TEntity>(ID));
        }

It’s shorter. Some might argue that it’s not as readable since it uses lambda syntax. In this case, I think DRY (don’t repeat yourself) is more important. To be honest, I’m not sure if this cuts the cyclomatic complexity of our methods in half, but It certainly lets us write about 1/2 as many tests and still have 100% coverage. We just need to write our already-have-a-transaction and wrap-in-transaction tests once period, instead of once per method.

If you’re working in C#, you can create another overload that accepts a System.Action (System.Func but returning void). In Visual Basic.NET, this would be a Sub, but unfortunately, there’s no lambda syntax for calling a Sub in VB.NET. To get around this, I’ve updated our Save method to this:

    Public Function Save(ByVal Entity As TEntity) As TEntity Implements ISave(Of TEntity).Save
        Return WrapInTransaction(Function() SaveOrUpdate(Entity))
    End Function

    Private Function SaveOrUpdate(ByVal Entity As TEntity) As TEntity
        m_Session.SaveOrUpdate(Entity)
        Return Entity
    End Function

Since we’re returning a value, we can use our existing WrapInTransaction(Func<>) method. The download includes tests for all of our DAO methods, including WrapInTransaction.

That’s it for part 10. For homework, write the tests for our StudentDaoImpl class. Hints: Use a fake, verify that it returns what it should, and verify that it doesn’t return what it shouldn’t.

I’ve changed the SQLiteDatabaseScope to match my previous post, and pulled it in to its own project.

Download the entire solution in VB.NET or C#.

In part 11, we’ll dive in to validation.

Jason

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.

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

This is part 4 of my series on ASP.NET MVC and NHibernate. If you’re not up to date, you can go check out:

  • Part 1 – Setting up the Visual Studio solution
  • Part 2 – Building the model
  • Part 3 – Mapping the model to the database

As promised, today, we’re going to test our mappings and get a little familiar with using NHibernate.

We’ll be using NUnit 2.5.2, but any recent version should work.

Disclaimer: I’m still learning some of this myself, so use at your own risk. This may not be considered best practice. Also, there’s almost certainly better ways to write these tests using one of the dozens of popular testing frameworks out there, but we’re using plain vanilla NUnit.

Let’s create a new Class Library project for our tests. We’ll call it NStackExample.Data.Tests. Now, in the data test project, add references to your core project, data project, NHibernate.dll, FluentNHibernate.dll, and NUnit.Framework.dll. If you’ve installed NUnit, NUnit.Framework.dll will be on the .NET tab. If you have multiple versions of NUnit installed, be sure to pick the right version.

SQL: Now in a convenient travel size

If you haven’t heard of SQLite before, you’re going to love this. It’s a tiny, self-contained, open-source SQL database engine in a DLL. Even better, it can run entirely in-memory and it’s blazing fast. Here’s how you get set up to use it:

  1. Download the SQLite ADO.NET Provider from here. Get the full version – the one named something like SQLLit-1.0.65.0-setup.exe. Install it, then grab a copy of System.Data.Sqlite.dll and put it in your Solution Items folder with all the other 3rd party libraries. If you’re running on a 64-bit operating system, grab the one from the bin\x64 directory. If not, use the one in bin.
  2. Download the SQLite library itself. Scroll down to Precompiled Binaries for Windows. It should be named something like sqlitedll-3_6_17.zip. Extract the SQLite3.dll to your Solution Items folder.
  3. In your data test project, add a reference to System.Data.SQLite.dll.
  4. Because SQLite3.dll was written in C and is completely unmanaged, we can’t add a direct reference to it. To ensure it gets put in the right place, we’re going to set it up as a content file. Right click on your data test project, choose Add Existing Item, then browse for SQLite3.dll. Add it. In Solution Explorer, it’ll be mixed in with the code for your project. Right click on it and choose properties. Set it to Copy Always. This will copy it to the bin\Debug or bin\Release folder every time your project is built, so it never gets forgotten.
  5. If you haven’t already, grab the code for the SQLiteDatabaseScope class from my previous post. Add it to your data test project.

A simple mapping test

Imports NUnit.Framework

<TestFixture()> _
Public Class CourseMappingTests

    <Test()> _
    Public Sub CanLoadAndSaveCourse()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession
                Dim ID As Guid
                Dim Course As Course

                Using Tran = Session.BeginTransaction
                    ID = Session.Save(New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3})
                    Tran.Commit()
                End Using
                Session.Clear()

                Using Tran = Session.BeginTransaction
                    Course = Session.Get(Of Course)(ID)

                    Assert.AreEqual("SUBJ", Course.Subject)
                    Assert.AreEqual("1234", Course.CourseNumber)
                    Assert.AreEqual("Title", Course.Title)
                    Assert.AreEqual("Description", Course.Description)
                    Assert.AreEqual(3, Course.Hours)

                    Tran.Commit()

                End Using

            End Using
        End Using
    End Sub

End Class
using System;
using NUnit.Framework;
using NHibernate;

namespace NStackExample.Data.Tests
{
    [TestFixture]
    public class CourseMappingTests
    {

        [Test]
        public void CanSaveAndLoadCourse()
        {
            using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    Guid ID;
                    Course Course;

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        ID = (Guid)Session.Save(new Course
                        {
                            Subject = "SUBJ",
                            CourseNumber = "1234",
                            Title = "Title",
                            Description = "Description",
                            Hours = 3
                        });
                        Tran.Commit();
                    }
                    Session.Clear();

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

                        Assert.AreEqual("SUBJ", Course.Subject);
                        Assert.AreEqual("1234", Course.CourseNumber);
                        Assert.AreEqual("Title", Course.Title);
                        Assert.AreEqual("Description", Course.Description);
                        Assert.AreEqual(3, Course.Hours);

                        Tran.Commit();
                    }
                }
            }
        }
    }
}

Here’s how it works:

  • First, we get a fresh in-memory SQLite database with our schema built.
  • Put a new course in the database
  • Clear the session
  • Get the course back out of the database
  • Check to make sure each of our properties survived the trip. If they didn’t, fail the test.

There’s a few things that may be new to you.

  • Our class has the TestFixture attribute. This tells NUnit that this class contains tests.
  • Each subroutine has the Test attribute. This tells NUnit that this method is a test.
  • The SQLiteDatabaseScope is almost certainly new, considering I wrote it Friday. You can read my previous post for more information.

Use of implicit transaction is discouraged

You’re probably wondering why I would wrap such simple one-statement database logic in a transaction, especially a Session.Get, which is essentially a single select statement. Prior to writing this series, I wouldn’t have done it that way. Rookie mistake.

While doing research for this entry, I ran across an example test from Ayende. He was using transactions on everything,  even his calls to Session.Get. I asked him why and he sent me a link to this NHProfiler Alert. It’s important and not obvious – at least not to me – so with permission, I’ve quoted the entire page.

A common mistake when using a database is to use transactions only when orchestrating several write statements. In reality, every operation that the database is doing is done inside a transaction, including queries and writes (update, insert, delete).

When we don’t define our own transactions, it falls back into implicit transaction mode, where every statement to the database runs in its own transaction, resulting in a large performance cost (database time to build and tear down transactions), and reduced consistency.

Even if we are only reading data, we should use a transaction, because using transactions ensures that we get consistent results from the database. Hibernate assumes that all access to the database is done under a transaction, and strongly discourages any use of the session without a transaction.

Session session = sessionFactory.openSession();
try {
  Transaction tx = session.beginTransaction();
  try {
    //execute code that uses the session
  } finally {
    tx.commit();
  }
} finally {
  session.close();
}

Leaving aside the safety issue of working with transactions, the assumption that transactions are costly and that we need to optimize them is false. As previously mentioned, databases are always running in a transaction. Also, they have been heavily optimized to work with transactions.

The real question here is: Is the transaction per-statement or per-batch? There is a non-trivial amount of work that needs to be done to create and dispose of a transaction; having to do it per-statement is more costly than doing it per-batch.

It is possible to control the number and type of locks that a transaction takes by changing the transaction isolation level (and, indeed, a common optimization is to reduce the isolation level).

Hibernate treats the call to commit() as the time to flush all changed items from the unit of work to the database, and without an explicit call to Commit(), it has no way of knowing when it should do that. A call to Flush() is possible, but it is frowned upon because this is usually a sign of improper transaction usage.

I strongly suggest that you use code similar to that shown above (or use another approach to transactions, such as TransactionScope, or Castle’s Automatic Transaction Management) in order to handle transactions correctly.

Transaction and the second level cache

Another implication of not using explicit transactions with Hibernate is related to the use of the second level cache.

Hibernate goes to great length in order to ensure that the 2nd level cache maintains a consistent view of the database. This is accomplished by deferring all 2nd level cache updates to the transaction commit. In this way, we can assert that the data in the 2nd level cache is the one committed to the database.

Forgoing the use of explicit transactions has the effect of nulling the 2nd level cache. Here is an example that would make this clear:

try {
  Post post = session.get(Post.class, 1);
  // do something with post
} finally {
  session.close();
}

Even if the 2nd level cache is enabled for Post, it is still not going to be cached in the 2nd level cache. The reason is that until we commit a transaction, Hibernate will not update the cache with the values for the loaded entities.

This code, however, does make use of the 2nd level cache:

Session session = sessionFactory.openSession();
try {
  Transaction tx = sessionFactory.beginTransaction();
  try {
    Post post = session.get(Post.class, 1);
    // do something with post
  } finally {
    tx.commit();
  }
} finally {
  session.close();
}

 

A slightly more complicated mapping test

When an entity has a required parent, as in the case of our section, you must create and insert the parent before actually testing the child. We’re not testing the cascade here. That’s a separate test. In this case, section has two required parents: a course, and a term. Here’s the test:

    <Test()> _
    Public Sub CanLoadAndSaveCourse()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession

                Dim ID As Guid
                Dim Section As Section
                Dim Course As New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3}

                Dim Term As New Term With { _
                        .Name = "Fall 2009", _
                        .StartDate = New Date(2009, 9, 1), _
                        .EndDate = New Date(2009, 12, 1)}

                'We're not testing the cascade, so save the parents first...
                Using Tran = Session.BeginTransaction
                    Session.Save(Course)
                    Session.Save(Term)
                    Tran.Commit()
                End Using
                Session.Clear()

                Using Tran = Session.BeginTransaction
                    ID = Session.Save(New Section With { _
                                      .Course = Course, _
                                      .FacultyName = "FacultyName", _
                                      .RoomNumber = "R1", _
                                      .SectionNumber = "1W", _
                                      .Term = Term})
                    Tran.Commit()
                End Using

                Session.Clear()

                Using Tran = Session.BeginTransaction
                    Section = Session.Get(Of Section)(ID)

                    Assert.AreEqual(Course, Section.Course)
                    Assert.AreEqual("FacultyName", Section.FacultyName)
                    Assert.AreEqual("R1", Section.RoomNumber)
                    Assert.AreEqual("1W", Section.SectionNumber)
                    Assert.AreEqual(Term, Section.Term)

                    Tran.Commit()

                End Using

            End Using
        End Using
    End Sub
        [Test]
        public void CanSaveAndLoadSection()
        {
            using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>) {
                using (ISession Session = Scope.OpenSession()) {

                    Guid ID;
                    Section Section;
                    Course Course = new Course {
                        Subject = "SUBJ",
                        CourseNumber = "1234",
                        Title = "Title",
                        Description = "Description",
                        Hours = 3};
                    Term Term = new Term {
                        Name = "Fall 2009",
                        StartDate = new DateTime(2009,8,1),
                        EndDate = new DateTime(2009,12,1)};

                    // We're not testing the cascade here, so explicitly save these parent objects.
                    using (ITransaction Tran = Session.BeginTransaction()) {
                        Session.Save(Course);
                        Session.Save(Term);
                        Tran.Commit();
                    }

                    Session.Clear();

                    using (ITransaction Tran = Session.BeginTransaction()) {
                        ID = (Guid) Session.Save(new Section {
                                 Course = Course,
                                 FacultyName = "FacultyName",
                                 RoomNumber = "R1",
                                 SectionNumber = "W1",
                                 Term = Term});
                        Tran.Commit();
                    }

                    Session.Clear();

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

                        Assert.AreEqual(Course, Section.Course);
                        Assert.AreEqual("FacultyName", Section.FacultyName);
                        Assert.AreEqual("R1",Section.RoomNumber);
                        Assert.AreEqual("W1", Section.SectionNumber);
                        Assert.AreEqual(Term, Section.Term);

                        Tran.Commit();
                    }

                }
            }

        }

Testing the cascade

“Cascade what? “

In your application, when you’ve just registered a student for a whole bunch of classes, usually with several changes along the way, you don’t want to have to remember what entities were added, removed or changed. That’s just crazy. Thanks to the Cascade functionality in NHibernate, you don’t have to do that. Just save the student entity. If your mappings are correct, it just works™.

For some people, especially me, that’s a big if. That’s why we test our mappings.

    <Test()> _
    Public Sub CanCascadeSaveFromCourseToSections()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession
                Dim ID As Guid

                Dim Term As New Term With { _
                        .Name = "Fall 2009", _
                        .StartDate = New Date(2009, 9, 1), _
                        .EndDate = New Date(2009, 12, 1)}

                'We're not testing the cascade of section -> term here
                Using Tran = Session.BeginTransaction
                    Session.Save(Term)
                    Tran.Commit()
                End Using
                Session.Clear()

                Dim Course As New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3}

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

                Dim Section2 As New Section With { _
                        .FacultyName = "FacultyName", _
                        .RoomNumber = "R1", _
                        .SectionNumber = "2", _
                        .Term = Term}

                Course.AddSection(Section1)
                Course.AddSection(Section2)

                'Test saving
                Using Tran = Session.BeginTransaction
                    ID = Session.Save(Course)
                    Tran.Commit()
                End Using
                Session.Clear()

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

                    Assert.AreEqual(2, Course.Sections.Count)
                    Assert.AreEqual(1, Course.Sections _
                                    .Where(Function(S As Section) _
                                               S.Equals(Section1)) _
                                    .Count(), "Course.Sections does not contain section 1.")

                    Assert.AreEqual(1, Course.Sections _
                                    .Where(Function(S As Section) _
                                               S.Equals(Section2)) _
                                    .Count(), "Course.Sections does not contain section 2.")

                    Tran.Commit()
                End Using
            End Using
        End Using
    End Sub
        [Test()]
        public void CanCascadeSaveFromCourseToSections()
        {
            using (SQLiteDatabaseScope Scope = new SQLiteDatabaseScope())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    Guid ID;

                    Term Term = new Term {
                                Name = "Fall 2009",
                                StartDate = new System.DateTime(2009, 9, 1),
                                EndDate = new System.DateTime(2009, 12, 1) };

                    //We're not testing the cascade of section -> term here
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Session.Save(Term);
                        Tran.Commit();
                    }
                    Session.Clear();

                    Course Course = new Course {
                        Subject = "SUBJ",
                        CourseNumber = "1234",
                        Title = "Title",
                        Description = "Description",
                        Hours = 3 };

                    Section Section1 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "1",
                        Term = Term };

                    Section Section2 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "2",
                        Term = Term };

                    Course.AddSection(Section1);
                    Course.AddSection(Section2);

                    //Test saving
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        ID = (Guid) Session.Save(Course);
                        Tran.Commit();
                    }
                    Session.Clear();

                    //Check the results
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Course = Session.Get(ID);

                        Assert.AreEqual(2, Course.Sections.Count);
                        Assert.AreEqual(1, Course.Sections
                                .Where(S => S.Equals(Section1)).Count(),
                                "Course.Sections does not contain section 1.");

                        Assert.AreEqual(1, Course.Sections
                                .Where(S => S.Equals(Section2)).Count(),
                                "Course.Sections does not contain section 2.");

                        Tran.Commit();
                    }
                }
            }
        }

The test above will make sure new and/or updated sections are saved when you save the course. Here’s how it works:

  • Get a fresh SQLite DB
  • Since we’re not testing terms, but we need one for our sections, build a term and stick it in the database.
  • Build a course and two sections.
  • Save the course
  • Clear the session
  • Get the course
  • Make sure it has our two sections

What should happen when you remove a section from a course? A parent course is required for each section. Remember, we specified not nullable in the mapping. More importantly, an orphaned section isn’t allowed in the real world. So, if a section is orphaned, it should be deleted. We need to write a test for that.

    <Test()> _
    Public Sub CanCascadeOrphanDeleteFromCourseToSections()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession
                Dim ID As Guid

                Dim Term As New Term With { _
                        .Name = "Fall 2009", _
                        .StartDate = New Date(2009, 9, 1), _
                        .EndDate = New Date(2009, 12, 1)}

                Using Tran = Session.BeginTransaction
                    'We're not testing the cascade of section -> term here
                    Session.Save(Term)
                    Tran.Commit()
                End Using
                Session.Clear()

                Dim Course As New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3}

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

                Dim Section2 As New Section With { _
                        .FacultyName = "FacultyName", _
                        .RoomNumber = "R1", _
                        .SectionNumber = "2", _
                        .Term = Term}

                Course.AddSection(Section1)
                Course.AddSection(Section2)

                Using Tran = Session.BeginTransaction
                    Session.Save(Course)
                    Tran.Commit()
                End Using
                Session.Clear()

                'Test removing
                Course.RemoveSection(Section1)
                Using Tran = Session.BeginTransaction
                    ID = Session.Save(Course)
                    Tran.Commit()
                End Using
                Session.Clear()

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

                    Assert.AreEqual(1, Course.Sections.Count())

                    Assert.AreEqual(0, Course.Sections _
                                    .Where(Function(S As Section) _
                                               S.Equals(Section1)) _
                                    .Count(), "Course.Sections still contains section 1")

                    Tran.Commit()
                End Using

            End Using
        End Using
    End Sub
        [Test()]
        public void CanCascadeOrphanDeleteFromCourseToSections()
        {
            using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    Guid ID;

                    Term Term = new Term {
                        Name = "Fall 2009",
                        StartDate = new System.DateTime(2009, 9, 1),
                        EndDate = new System.DateTime(2009, 12, 1) };

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        //We're not testing the cascade of section -> term here
                        Session.Save(Term);
                        Tran.Commit();
                    }
                    Session.Clear();

                    Course Course = new Course {
                        Subject = "SUBJ",
                        CourseNumber = "1234",
                        Title = "Title",
                        Description = "Description",
                        Hours = 3 };

                    Section Section1 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "1",
                        Term = Term };

                    Section Section2 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "2",
                        Term = Term };

                    Course.AddSection(Section1);
                    Course.AddSection(Section2);

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Session.Save(Course);
                        Tran.Commit();
                    }
                    Session.Clear();

                    //Test removing
                    Course.RemoveSection(Section1);
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        ID = (Guid) Session.Save(Course);
                        Tran.Commit();
                    }
                    Session.Clear();

                    //Check the results
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Course = Session.Get<Course>(ID);

                        Assert.AreEqual(1, Course.Sections.Count());

                        Assert.AreEqual(0, Course.Sections
                            .Where(S => S.Equals(Section1)).Count(),
                            "Course.Sections still contains section 1");

                        Tran.Commit();

                    }
                }
            }
        }

I hope you see where I’m going with this one. Except for query tests, which we’ll do when we write our DAOs, that’s it for NHibernate testing. We do the same types of tests for our other entity classes.

But…

So, I bet you’re thinking “This mess won’t compile and even if it did, almost all of your tests would fail!” Yep. If the tests always pass, why write them?

Normally, I’d at least declare those missing functions so the solution would compile, but in this case, the discussion of those issues fits better with our next topic: How do we fix the broken stuff?

Download links for the complete solution in both languages are coming soon.

Edit: Download VB.NET here or C# here. To simplify things, I’ve removed the StudentTerm entity from the model.

Jason

- Testy and in need of sleep.

References: Ayende’s blog post, Krzysztof Kozmic’s recent post on Devlio.us, Daniel Hoebling’s blog post, Ayende’s NHProfiler Alerts.

Tags: