Sunday, January 29, 2012

Simple Circles–NHibernate Repository (Part 7a)

This post is part of a series on building a simple to use web-based contact and customer relationship management application. The goal is to support various audiences including businesses, teams, clubs, religious organizations, etc.
I’ve got a decent foundation in place with the domain model, persistence interface defining the repository pattern and a concrete implementation with a fake repository for testing purposes. The fake repository also serves to easily and quickly validate the design – if it is difficult to implement with simple in-memory structures then it’ll likely be even harder using an ORM. I have more experience with NHibernate and it’s a more sophisticated and mature ORM so I’ll start with it first.
To begin with, I’ve added two interfaces to the Persistence class library to define a database session and a factory pattern to manage creating sessions.
namespace Circles.Persistence
{
  using System;
  using System.Data;

  public interface IDbSession : IDisposable
  {
    void BeginTransaction(IsolationLevel isolationLevel);

    void Commit();

    void Rollback();

    IPartyRepository CreatePartyRepository();
  }
}
Bob Cravens provided inspiration for the session work with his Truck Management System. that eventually made it into his own GenericRepository project. This database session interface specifies that concrete implementations provide BeginTransaction, Commit, and Rollback methods as well as a concrete implementation for creating a PartyRepository.
The DbSessionFactory defines the factory pattern with a little bit of flexibility – you can create a DbSession with or without transactional support.
namespace Circles.Persistence
{
  public interface IDbSessionFactory
  {
    IDbSession Create(bool withTransaction);
  }
}
Simple Circles - Install NHibernateNext I created an NHibernateRepository class library project and added the NHibernate package via NuGet. While NuGet makes it simple to install packages and their dependencies, it doesn’t relieve you from managing versions. It is usually best to specify an explicit version when installing packages to ensure you don’t get conflicts and other surprises. In my case I used “-version 3.1.0.4000” which also installs the same version of Iesi.Collections.
The implementation of DbSessionFactory is pretty lightweight and trivial:
namespace Circles.NHibernateRepository
{
  using System.Data;
  using Circles.Persistence;

  public class DbSessionFactory : IDbSessionFactory
  {
    public IDbSession Create(bool withTransaction)
    {
      DbSession session = new DbSession(SessionProvider.SessionFactory);

      if (withTransaction)
      {
        session.BeginTransaction(IsolationLevel.ReadCommitted);
      }

      return session;
    }
  }
}

The SessionProvider class used in line #10 above will be discussed below. DbSession is also lightweight because it delegates the heavy lifting to the underlying NHibernate methods:
namespace Circles.NHibernateRepository
{
  using System;
  using System.Data;
  using Circles.Persistence;
  using NHibernate;

  public class DbSession : IDbSession
  {
    private readonly ISession session;
    private ITransaction transaction;

    public DbSession(ISessionFactory sessionFactory)
    {
      if (sessionFactory == null)
      {
        throw new ArgumentNullException("sessionFactory");
      }

      this.session = sessionFactory.OpenSession();
      this.session.FlushMode = FlushMode.Auto;
    }

    ~DbSession()
    {
      this.Dispose();
    }

    public void Dispose()
    {
      if (this.session == null)
      {
        return;
      }

      lock (this.session)
      {
        if (this.session.IsOpen)
        {
          this.session.Close();
        }
      }

      GC.SuppressFinalize(this);
    }

    public IPartyRepository CreatePartyRepository()
    {
      return new PartyRepository(this.session);
    }

    public void BeginTransaction(IsolationLevel isolationLevel)
    {
      this.transaction = this.session.BeginTransaction(isolationLevel);
    }

    public void Commit()
    {
      if (this.transaction == null)
      {
        return;
      }

      if (!this.transaction.IsActive)
      {
        throw new InvalidOperationException("No active transation");
      }

      this.transaction.Commit();
    }

    public void Rollback()
    {
      if (this.transaction == null)
      {
        return;
      }

      if (this.transaction.IsActive)
      {
        this.transaction.Rollback();
      }
    }
  }
}

NHibernate is highly configurable so instantiating the libraries requires processing configuration settings contained in hibernate.cfg.xml as well as reflecting over your assemblies. Doing so is expensive – its best to cache the initialized configuration and session for the lifetime of the application. The SessionProvider class does this:
namespace Circles.NHibernateRepository
{
  using NHibernate;
  using NHibernate.Cfg;

  public class SessionProvider
  {
    private static Configuration configuration;

    private static ISessionFactory sessionFactory;

    private SessionProvider()
    {
    }

    public static Configuration Configuration
    {
      get
      {
        if (configuration == null)
        {
          configuration = new Configuration();
          configuration.Configure();
          configuration.AddAssembly(typeof(PartyRepository).Assembly);
        }

        return configuration;
      }
    }

    public static ISessionFactory SessionFactory
    {
      get { return sessionFactory 
              ?? (sessionFactory = Configuration.BuildSessionFactory()); }
    }

    public static ISession GetSession()
    {
      return SessionFactory.OpenSession();
    }
  }
}

That’s a lot of implementation and setup plumbing! The source code for this article can be downloaded from here. The next installment will map the domain model to the relational database model.

No comments:

Post a Comment