Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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;
    }
}
share|improve this question

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.