My goal was to use the NHibernate Session and to create a UnitOfWork class that I could use thoughout my project.
My goals are:
- Calling Commit should commit everything including un-flushed state (was mostly out of the box I believe)
- Rollback should roll back everything including whatever was not flushed from the scope yet
- Disposing the UnitOfWork without a Commit or a Rollback should not persist any changes to the database.
- I wanted to wrap flush so everyone in my team understood what it was doing.
- And also expose various functions of the NHibernate session that I want to use in my project.
My project requires:
- This to work with WebApi where I use an ActionFilterAttribute to begin commit the unit of work.
- This to work with Mvc where I use an ActionFilterAttribute to begin commit the unit of work.
- In a console application that is used for background processes. In this use-case, I need to be able to begin and end multiple transactions during a single execution of the jobs that it runs.
Some additional information:
- I'm using Autofac, so the ISession being injected is just a Vanilla NHibernate Session.
I would love some constructive criticism on the approach.
public class UnitOfWorkSession : IUnitOfWorkSession
{
private ISessionFactory _sessionFactory;
private readonly IEnumerable<IChangeTracker<Member>> _memberChangeTrackers;
private readonly IEnumerable<IChangeTracker<ForumThread>> _forumThreadChangeTrackers;
private readonly IEnumerable<IChangeTracker<Comment>> _commentTrackers;
private ISession _session { get; set; }
private bool _committed { get; set; }
private bool _rollback { get; set; }
private bool _isActive = false;
public UnitOfWorkSession(ISessionFactory sessionFactory, IEnumerable<IChangeTracker<Member>> memberChangeTrackers, IEnumerable<IChangeTracker<ForumThread>> forumThreadChangeTrackers, IEnumerable<IChangeTracker<Comment>> commentTrackers)
{
_sessionFactory = sessionFactory;
_memberChangeTrackers = memberChangeTrackers;
_forumThreadChangeTrackers = forumThreadChangeTrackers;
_commentTrackers = commentTrackers;
}
private ISession Session
{
get { return _session; }
}
public ITransaction BeginNewUnitOfWork()
{
if (_isActive)
throw new InvalidOperationException("There is already an active Unit of work");
// Every unit of work will use a fresh NHibernate session. Committing or Rollback will dispose this session.
_session = _sessionFactory.OpenSession(new AuditInterceptor(_memberChangeTrackers, _forumThreadChangeTrackers, _commentTrackers));
var newTransaction = _session.BeginTransaction();
_isActive = true;
_committed = false;
_rollback = false;
return newTransaction;
}
public void Commit()
{
if (_session.Transaction.IsActive)
{
try
{
_session.Transaction.Commit();
}
catch
{
_session.Transaction.Rollback();
throw;
}
finally
{
_committed = true;
_isActive = false;
_session.Dispose();
}
}
}
/* Used to manually rollback a transaction/prevent a commit */
public void RollBack()
{
try
{
_session.Clear();
_session.Transaction.Rollback();
}
finally
{
_isActive = false;
_rollback = true;
_session.Dispose();
}
}
public void Save(object entity)
{
_session.Save(entity);
}
public void Update(object entity)
{
_session.Update(entity);
}
public void SaveOrUpdate(object entity)
{
_session.SaveOrUpdate(entity);
}
public void Delete(string sqlStatement)
{
_session.Delete(sqlStatement);
}
public void Delete(object entity)
{
_session.Delete(entity);
}
public void Delete(string sqlStatement, Guid id)
{
if (sqlStatement != null && sqlStatement.IndexOf("?") == -1)
throw new InvalidParameterValueException("sqlStatement","Must have a ? mark in the sql statement to inject the id");
_session.Delete(sqlStatement, id, NHibernateUtil.Guid);
}
public T Get<T>(object id, LockMode lockMode = null)
{
if (lockMode != null)
return _session.Get<T>(id, lockMode);
return _session.Get<T>(id);
}
public ISQLQuery CreateSQLQuery(string queryString)
{
return _session.CreateSQLQuery(queryString);
}
public IQueryable<T> Query<T>()
{
return _session.Query<T>();
}
// Note: All this does is take the changes from nhibernate session and put it on the current un-committed transaction of that session.
// This should be used very sparingly in cases where a conflict can occur in the Session.
// Example of use: See SaveQuizResultCommand, where it is used to prevent a unique key violation occurring.
public void FlushToActiveTransaction(bool clearSession = false)
{
_session.Flush();
if (clearSession)
_session.Clear();
}
public void Clear()
{
_session.Clear();
}
public IStatistics Statistics
{
get { return _session.SessionFactory.Statistics; }
}
public void Dispose()
{
if (Session != null && Session.IsOpen && !_committed && !_rollback && Session.Transaction.IsActive)
Session.Transaction.Rollback();
}
public bool IsDirty()
{
return _session.IsDirty();
}
public bool IsOpen()
{
return _session.IsOpen;
}
}