In a relative short span of time nHibernate has become a major member of my toolbox. It has become the way to work with a database, not only a small hap-snap apps but it's also making big strides into a constantly evolving system I'm working on. Setting up nHibernate several times has given me the opportunity to do some playing around. Browsing around you can find several ways to do it, some ways just focus on getting it done, some ways focus on performance, and some ways focus on resource management. Here I would like to discuss my way where I tried to take all aspects in account. Don't take it as the way; please do share your thoughts.
<update>After a very good comment by rui I have corrected some of the code for the repository part. Thanks ! Blogging is just the best public code review you can get :)</update>
Code will work with two very important nHibernate objects
- The sessionfactory. This object swallows the configuration, from the mappings to the connectionstring to the database. Instantiating it is quite a job as it has to parse all mappings and check for corresponding domain classes. The good thing is that it will actively check all mappings; when the sessionfactory is successfully instantiated you know you're more than halfway. (Note that the sessionfactory does not check the database, in case there are errors in columns names of the mappings or in the connectionstring your code will not hit an exception until it actually tries to connect to the database). The sessionfactory is thread-safe all your code can work with one and the same sessionfactory. And as it is expensive to create the thing it pays off to create it only once.
- The session. The sessionfactory provides you with session objects to work with the database. A session wraps up an actual connection to the database, so it holds an expensive unmanged resource. The mantra for "classical" ado.net code was always to open the connection as late as possible and to close immediately after firing your sql. More details on that here. The nHibernate sessionfactory provides session objects containing an open connection to the database. The session object has a method to close the connection, but there are several reasons not to do that. The most obvious one is lazy loading. For example when you load an invoice object from the database nHibernate will not load the data of the associated customer until your code actively touches the customer's properties. In case your code doesn't touch the customer the data will not be loaded. To load the data from the database nHibernate needs an open connection. In case you had already closed the session your code will hit a LazyLoadException. So a session object holds an open connection to the database, which requires extra care.
I'm going to manage both the sessionfactory and the session in one nHibernateHelper class. The code needs only one sessionfactory, which has to be instantiated once. The sessionfactory is going to be a static (shared) member of the class. I took the idea to instantiate the sessionfactory in a static constructor from an nice article on theserverside. A static constructor fires the moment the class is loaded. That is the moment the class is first touched by running code. In the original code the configuration is loaded from a configuration file. My problem with that is that I want to be independent from that for reasons of testability and custom configuration. Most settings can be hard coded but something like the database connectionstring has to be set from code. A static constructor does not have any parameters and I can not set any members, the moment I touch the class the static constructor fires first and I'm too late. What does work is a small helper class.
internal class nHibernateConnectionHelper
{
internal static string connectionString = "";
}
internal class NhibernateHelper : INhibernateHelper
{
private static readonly ISessionFactory sessionFactory;
static NhibernateHelper()
{
Configuration cfg = new Configuration();
IDictionary props = new Hashtable();
props.Add("hibernate.dialect", "NHibernate.Dialect.MsSql2005Dialect");
props.Add("hibernate.connection.provider", "NHibernate.Connection.DriverConnectionProvider");
props.Add("hibernate.connection.connection_string", nHibernateConnectionHelper.connectionString);
cfg.SetProperties(props);
cfg.AddAssembly("dlcr.domain");
sessionFactory = cfg.BuildSessionFactory();
}
When the nHibernateConnectionHelperClass is touched first the static constructor of the NhibernateHelper class reads the updated connectionstring. It is used like this:
public class NhibernateHelperFactory
{
public static INhibernateHelper CreateHelper(string dbConnection)
{
nHibernateConnectionHelper.connectionString = dbConnection;
return new NhibernateHelper();
}
}
Now the static constructor works as intended. This trick also works when both classes are in the same cs file. How the sessionfactory itself is set up is in a previous post.
With the sessionfactory ready the nHibernatehelper can start its core business: managing a session. A new session is instantiated and opened in the instance constructor. This constructor performs some checks whether the sessionfactory will provide a session with the expected database. It reads the connectionstring from the sessionfactory property as described in the previous post.
internal NhibernateHelper()
{
if (string.IsNullOrEmpty(nHibernateConnectionHelper.connectionString))
throw new Exception("Sessionfactory needs a connectionstring");
if (nHibernateConnectionHelper.connectionString != connectionString)
throw new Exception(string.Format("Sessionfactory connection allready initialized as {0}",connectionString));
currentSession = sessionFactory.OpenSession();
}
As my nHibernateHelper objects wrap up an unmanaged resource (the database connection in the currentSession object) it has to implement IDisposable. The session object itself also implements IDisposable so the helper's implementation can be pretty straightforward.
public void Dispose()
{
currentSession.Dispose();
}
The only thing exposed is the nHibernateHelperFactory which servers IHibernateHelper objects. My IHibernateHelper is pretty straightforward
public interface INhibernateHelper : IDisposable
{
IQuery Query(string className);
IQuery Query(string className, string propertyName, string propertyValue);
IQuery Query(string className, string propertyName, long propertyValue);
IQuery Query(string className, string whereClause);
void Save(object domainObject);
void Save(object[] domainObjects);
void Delete(object domainObject);
}
It has methods to query, save or delete objects. These are the methods my repository needs to interact with the database. This is a sample repository
public class Repository
{
private readonly INhibernateHelper nhh;
public Repository(INhibernateHelper nHhibernateHelper)
{
nhh = nHhibernateHelper;
}
public IList<IBoeking> Boekingen()
{
return nhh.Query("Boeking").List<IBoeking>();
}
public IBTWpercentage BTWpercentage(string naam)
{
IQuery query = nhh.Query("BTWpercentage", "Omschrijving", naam);
return query.UniqueResult<IBTWpercentage>();
}
public void Save(object o)
{
nhh.Save(o);
}
}
The helper is injected in the repository's constructor. For testing purposes I could also inject a mock.
The overall result is that by using a disposable helper I'm in full control of the connection to the database and I have all nHibernate's benefits like lazy loading.
[Test]
public void CanDemoHibernate()
{
using (INhibernateHelper nhl = NhibernateHelperFactory.CreateHelper("Data Source=.;Initial Catalog=DLCR;Integrated Security=True"))
{
Repository rps = new Repository(nhl)
IBoeking bks = StubFactory.BoekingStub();
IBTWpercentage bts = StubFactory.BTWstub();
Boeking bk = new Boeking();
bk.Omschrijving = bks.Omschrijving;
bk.Totaal = bks.Totaal;
bk.BTW = bts.Percentage;
rps.Save(bk);
// Lazy load ahead
ISet<BoekingsRegel> regels = bk.Regels;
foreach (BoekingsRegel regel in regels)
{
Console.Write(regel.DeDato);
tab();
Console.Write(regel.Links);
tab();
Console.WriteLine(regel.Rechts);
}
Assert.Greater(regels.Count, 0);
}
}
By using the helper I will be sure the session is always properly closed, also when an exception is hit.
Perhaps I'm somewhat over concerned when it comes to keeping control over open connections. Once I doubted ado.net's connection pooling only to find out that works very well. Also nHibernate does a good job of keeping the number of open connection's under control. When I open multiple repositories each using it's own nHibernateHelper object these will all use the same connection. My main point is that my helper gives me the feeling I'm still in control without sacrificing performance or functionality.