I have a persistence framework built on top of NHibernate that is used in a few Web apps. It hides the NH implementation behind an IRepository
and IRepository<T>
interface, with the concrete instances provided by Unity (thus I could in theory swap out NHibernate for, say, Entity Framework fairly easily).
Since Unity doesn't (or at least the version I'm using doesn't) support the passing in of constructor parameters other than those that are dependency injections themselves, passing in an extant NH ISession isn't possible; but I do want all objects in the UOW to share the same ISession.
I solve this by having a controlling repository class that manages access to the ISession on a per-thread basis:
public static ISession Session
{
get
{
lock (_lockObject)
{
// if a cached session exists, we'll use it
if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
{
return (ISession)PersistenceFrameworkContext.Current.Items[NHibernateRepository.SESSION_KEY];
}
else
{
// must create a new session - note we're not caching the new session here... that's the job of
// BeginUnitOfWork().
return _factory.OpenSession(new NHibernateInterceptor());
}
}
}
}
In this example, PersistenceFrameworkContext.Current.Items
accesses an IList<object>
that is stored either ThreadStatic
if not in a Web context, or within HttpContext.Current.Items
if it is in a Web context (to avoid thread-pool problems). The first call to the property instantiates the ISession
from the stored factory instance, subsequent calls just retrieve it from storage. The locking will slow things down slightly but not as much as just locking an appdomain-scoped static ISession
instance.
I then have BeginUnitOfWork
and EndUnitOfWork
methods to take care of the UOW - I have specifically disallowed nested UOWs because frankly they were a pain to manage.
public void BeginUnitOfWork()
{
lock (_lockObject)
{
if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
EndUnitOfWork();
ISession session = Session;
PersistenceFrameworkContext.Current.Items.Add(SESSION_KEY, session);
}
}
public void EndUnitOfWork()
{
lock (_lockObject)
{
if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
{
ISession session = (ISession)PersistenceFrameworkContext.Current.Items[SESSION_KEY];
PersistenceFrameworkContext.Current.Items.Remove(SESSION_KEY);
session.Flush();
session.Dispose();
}
}
}
Finally, a pair of methods provide access to the domain-type-specific repositories:
public IRepository<T> For<T>()
where T : PersistentObject<T>
{
return Container.Resolve<IRepository<T>>();
}
public TRepository For<T, TRepository>()
where T : PersistentObject<T>
where TRepository : IRepository<T>
{
return Container.Resolve<TRepository>();
}
(Here, PersistentObject<T>
is a base class providing ID and Equals support.)
Access to a given repository is thus in the pattern
NHibernateRepository.For<MyDomainType>().Save();
This is then facaded over such that you can use
MyDomainType.Repository.Save();
Where a given type has a specialised repository (ie needs more than it can get from IRepository<T>
) then I create an interface deriving from IRepository<T>
, an extending implementation inheriting from my IRepository<T>
implementation, and in the domain type itself I override the static Repository
property using new
new public static IUserRepository Repository
{
get
{
return MyApplication.Repository.For<User, IUserRepository>();
}
}
(MyApplication
[which is called something less noddy in the real product] is a facade class which takes care of supplying the Repository
instance via Unity so you have no dependency on the specific NHibernate repository implementation within your domain classes.)
This gives me full pluggability via Unity for the repository implementation, easy access to the repository in code without jumping through hoops, and transparent, per-thread ISession
management.
There's lots more code than just what's above (and I've simplified the example code a great deal), but you get the general idea.
MyApplication.Repository.BeginUnitOfWork();
User user = User.Repository.FindByEmail("[email protected]");
user.FirstName = "Joe"; // change something
user.LastName = "Bloggs";
// you *can* call User.Repository.Save(user), but you don't need to, because...
MyApplication.Repository.EndUnitOfWork();
// ...causes session flush which saves the changes automatically
In my Web app, I have session-per-request, so BeginUnitOfWork
and EndUnitOfWork
get called in BeginRequest
and EndRequest
respectively.