0

I want to move my Unit of work away from my business logic.

In Infrastructure.Data I have

NHibernateHelper

public class NHibernateHelper
{
    private ISessionFactory _sessionFactory;
    private readonly string _connectionString;

    public NHibernateHelper (string connectionString)
    {
        if (string.IsNullOrEmpty (connectionString))
            throw new HibernateConfigException ("ConnectionString in Web.config is not set.");

        _connectionString = connectionString;
    }

    public ISessionFactory SessionFactory {
        get {
            return _sessionFactory ?? (_sessionFactory = InitializeSessionFactory ());
        }
    }

    private ISessionFactory InitializeSessionFactory ()
    {
        return Fluently.Configure ()
            .Database (PostgreSQLConfiguration.Standard.ConnectionString (_connectionString).
                Dialect ("NHibernate.Dialect.PostgreSQL82Dialect"))
        // Use class mappings
            .Mappings (m => m.FluentMappings.AddFromAssembly (Assembly.GetExecutingAssembly ()))
        // Will Update and create tables if does not exist
            .ExposeConfiguration (cfg => new SchemaUpdate (cfg).Execute (true, true))
            .BuildSessionFactory ();
    }
}

UnitOfWork

public class UnitOfWork : IUnitOfWork
{
    private readonly ISessionFactory _sessionFactory;
    private readonly ITransaction _transaction;

    public ISession Session { get; private set; }

    public UnitOfWork (ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
        Session = _sessionFactory.OpenSession ();
        Session.FlushMode = FlushMode.Auto;
        _transaction = Session.BeginTransaction (IsolationLevel.ReadCommitted);
    }

    public void Commit ()
    {
        if (!_transaction.IsActive) {
            throw new InvalidOperationException ("Oops! We don't have an active transaction");
        }
        _transaction.Commit ();
    }

    public void Rollback ()
    {
        if (_transaction.IsActive) {
            _transaction.Rollback ();
        }
    }

    public void Dispose ()
    {
        if (Session.IsOpen) {
            Session.Close ();
            Session = null;
        }
    }
}

Repository

public class Repository<TEntity> : IReadWriteRepository<TEntity>
    where TEntity : class
{
    private readonly ISession _session;

    public Repository (ISession session)
    {
        _session = session;
    }

    #region IWriteRepository

    public bool Add (TEntity entity)
    {
        _session.Save (entity);
        return true;
    }

    public bool Add (System.Collections.Generic.IEnumerable<TEntity> entities)
    {
        foreach (TEntity entity in entities) {
            _session.Save (entity);
        }
        return true;
    }

    public bool Update (TEntity entity)
    {
        _session.Update (entity);
        return true;
    }

    public bool Update (System.Collections.Generic.IEnumerable<TEntity> entities)
    {
        foreach (TEntity entity in entities) {
            _session.Update (entity);
        }
        return true;
    }

    public bool Delete (TEntity entity)
    {
        _session.Delete (entity);
        return true;
    }

    public bool Delete (System.Collections.Generic.IEnumerable<TEntity> entities)
    {
        foreach (TEntity entity in entities) {
            _session.Delete (entity);
        }
        return true;
    }

    #endregion

    #region IReadRepository

    public System.Linq.IQueryable<TEntity> All ()
    {
        return _session.Query<TEntity> ();
    }

    public TEntity FindBy (System.Linq.Expressions.Expression<System.Func<TEntity, bool>> expression)
    {
        return FilterBy (expression).SingleOrDefault ();
    }

    public TEntity FindBy (object id)
    {
        return _session.Get<TEntity> (id);
    }

    public System.Linq.IQueryable<TEntity> FilterBy (System.Linq.Expressions.Expression<System.Func<TEntity, bool>> expression)
    {
        return All ().Where (expression).AsQueryable ();
    }

    #endregion
}

In Intrastructure.DependencyInjectrion I have:

    public void RegisterServices (SimpleInjector.Container container)
    {

        var connectionSettings = ConfigurationManager.ConnectionStrings ["Connection"];

        container.RegisterPerWebRequest<ISessionFactory> (() => {
            NHibernateHelper objNHibernate = new NHibernateHelper (connectionSettings.ConnectionString);
            return objNHibernate.SessionFactory;
        });


        container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork> ();

        container.RegisterPerWebRequest<ISession> (() => {

            UnitOfWork unitOfWork = (UnitOfWork)container.GetInstance<IUnitOfWork> ();
            return unitOfWork.Session;

        });

        container.RegisterOpenGeneric (typeof(IReadWriteRepository<>), typeof(Repository<>));

    }

Then in my service I would do something like this:

Web.UI.Services.CompanyService

    public void CreateNewCompany (Company company)
    {
        if (_companyRepository.Add (company))
            _unitOfWork.Commit ();
        else
            _unitOfWork.Rollback ();
    }

Would it be a better practice to call _unitOfWork.Commit() or _unitOfWork.Rollback() in the eneric Repository rather than in the in the Service layer?

I was thinking of improving the generic Repository by injecting the IUnitOfWork into it while adding some additional error handling in too.

If this is not a good approach, can someone give me some direction to improve this? Note: I do want to keep repository pattern in case we choose to switch ORM in a few years time.

Shane van Wyk
  • 1,870
  • 1
  • 26
  • 62

1 Answers1

1

Managing transactions in the repository is definitely not the standard way to do it, as it removes the possibility to implement business logic that spans multiple repositories (or multiple actions on the same repository) and is required to be executed atomically.

I would try to keep the transaction management on the top-most layer that makes sense, i.e. that does not impair your ability to re-use business logic (including transaction management) if you decided to host your application in a different environment for example. This would seem to be the service layer in your case; you could also distinguish between different types of services as done in Domain-Driven Design where there is a difference between application services and domain services. The application services might orchestrate multiple domain services and handle the management of transactions and potentially the unit of work).

Hope this helps a bit.

PermaFrost
  • 1,386
  • 12
  • 10
  • That sort of makes sense. Would you say that the way unit of work is implemented at the moment is fine then? and to just leave it as it is? I will go read a bit more of DDD, Think I missed something haha – Shane van Wyk Mar 05 '15 at 22:17
  • I would say the Unit of Work implementation looks generally fine to me. I suppose an instance is created per unit of work (whatever that may be, a HTTP request perhaps). The only thing you could perhaps ponder about is whether you want to implement a finalizer that disposes the unit of work plus whether you want to check for active transactions on dispose (if there is an active transaction, do a rollback and log an error or something; this would prevent open transactions - not sure if `Session.Close()` takes care of this already). – PermaFrost Mar 05 '15 at 23:12
  • Additionally I would personally use the Unit of Work a bit differently, I would not rollback a transaction if I can be sure that nothing has been changed as done in the service code you posted. Commits are always cheaper than rollbacks on RDBMS. – PermaFrost Mar 05 '15 at 23:14
  • I have updated the post. I use a `RegisterPerRequest` method as part of the Simple Injector dependency injection. I will go and review my code and make some changes. – Shane van Wyk Mar 06 '15 at 00:11