In my previous post, I talked about my base repository class that I use, to abstract the NHibernate details away from the actual repository. Now that I have the Do and DoTransaction methods to further the abstraction, I thought it would be good to share my whole abstraction.
using System;
using NHibernate;
using NHibernate.Cfg;
namespace RepositoryBase
{
public abstract class Repository : IDisposable
{
#region Vars
private static readonly Configuration _config;
private static readonly ISessionFactory _factory;
#endregion
#region Constructor / Destructor
static BaseDAL()
{ _config = new Configuration();
_config.Configure(typeof(BaseDAL).Assembly, "RepositoryBase.hibernate.cfg.xml");
_factory = _config.BuildSessionFactory();
}
~BaseDAL()
{ Dispose();
}
#endregion
#region Properties
public ISession Session { get; private set; }
public ITransaction Transaction { get; private set; }
#endregion
#region Methods
protected void Do(Action unitOfWork)
{ try
{ OpenSession();
unitOfWork();
}
finally
{ CloseSession();
}
}
protected void DoTransaction(Action unitOfWork)
{ try
{ OpenSession();
BeginTransaction();
if (unitOfWork != null)
unitOfWork();
CommitTransaction();
}
catch
{ RollbackTransaction();
throw;
}
finally
{ CloseSession();
}
}
#endregion
#region Helper Methods
private void OpenSession()
{ if (Session != null) return;
Session = _factory.OpenSession();
Session.FlushMode = FlushMode.Auto;
}
private void CloseSession()
{ if (Session == null) return;
if (Session.IsOpen)
{ Session.Close();
}
Session.Dispose();
Session = null;
}
private void BeginTransaction()
{ ValidateSession();
Transaction = Session.BeginTransaction();
}
private void CommitTransaction()
{ ValidateSession();
if (Transaction != null)
Transaction.Commit();
CloseTransaction();
}
private void RollbackTransaction()
{ if (Transaction != null)
Transaction.Rollback();
CloseTransaction();
}
private void ValidateSession()
{ if (Session == null)
throw new ApplicationException("NHibernate Session Not Open."); }
private void CloseTransaction()
{ if (Transaction == null) return;
Transaction.Dispose();
Transaction = null;
}
#endregion
#region IDisposable Members
public void Dispose()
{ CloseTransaction();
CloseSession();
}
#endregion
}
}
Note that I am using an embedded resource as my "hibernate.cfg.xml" location. Just change this line to use a file system resource, or whatever you want for your hibernate configuration.
I'm still wanting to re-abstract this into a set of objects that let's me be concerned with transactions and queries at a business level. One step at a time, though. The syntax that I would like to see, at the moment, would be something like this:
public void SomeBusinessValue()
{ Something something = DoSomething.BusinessRelated();
SomethingElse somethingElse = SomethingElse();
Repository.DoTransaction(() =>
{ SomeRepository.Save(something);
SomeOtherRepository.Delete(somethingElse);
});
}
And if I get really ambitious, I may try to incorporate Castle.Windsor's Automatic Transaction Facility, so that I can have syntax like this:
[Transactional(Transaction.Requires)]
public void SomeBusinessValue()
{ Something something = DoSomething.BusinessRelated();
SomethingElse somethingElse = SomethingElse();
SomeRepository.Save(something);
SomeOtherRepository.Delete(somethingElse);
}
Of course, the more I travel down this path, the more obvious it is how Rhino.Commons' Repository came along. Heh - I'm only about 2 steps away from completely re-doing Ayende's UnitOfWork.
In fact, I may try to re-use NHibernateQueryGenerator and go for this syntax:
[Transactional(Transaction.Requires)]
public void SomeBusinessValue()
{ Something something = Repository<Something>.Find(Where.SomeProperty = someValue);
Something something = DoSomething.BusinessRelated();
SomethingElse somethingElse = SomethingElse();
Repository<Something>.Save(something);
Repository<SomethingElse>.Delete(somethingElse);
}
Wouldn't that be fun... I really do like this syntax. In fact, I spent 9+ months working with it on a project because I love the simplicity of the syntax. In the end, though, I did not understand all of the underlying architecture and abstraction and Ayende set up (he includes the ability to plug in any DAL, including Castle.ActiveRecord, NHibernate, etc) and it caused headaches for me.
...
The major problem I have with this syntax, at the moment, is unit testing the Repository object. I either need to hide these details behind an IWhateverRepository interface (complete with Save, Delete and GetByWhatever methods) like I have been doing, or I I'll need to learn how to unit test NHibernate with in-memory database or something... We'll see where this leads.
(side note: how's that for a "ThoughtStream".
)