6
\$\begingroup\$

I have a Unit of Work / Repository pattern in place to abstract away some nHibernate. Most examples I've seen though use the pattern a little differently.

In particular, in the constructor for UnitOfWork, most examples start the transaction immediately and have one transaction per unit of work. Whereas I created a BeginTransaction method so I can do something like,

using(var tx = unitOfWork.BeginTransaction())
{
    // Do transactional things here!
}

Is there a reason why others instantiate their transaction per unit of work?

IUnitOfWork

public interface IUnitOfWork : IDisposable
{
    IReadWriteRepository<TEntity> GetRepository<TEntity>() where TEntity : class;
    void Commit();
    void Rollback();
}

UnitOfWork

public class UnitOfWork : IUnitOfWork
{
    private readonly ISessionFactory _sessionFactory;
    private ITransaction _transaction;
    private Dictionary<Type, object> _repositories;

    public ISession Session { get; private set; }

    public UnitOfWork(ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
        Session = _sessionFactory.OpenSession();
    }

    public IReadWriteRepository<TEntity> GetRepository<TEntity>() where TEntity : class
    {
        foreach (var key in _repositories.Keys)
        {
            if (key == typeof(TEntity))
            {
                return _repositories[typeof(TEntity)] as IReadWriteRepository<TEntity>;
            }
        }

        var repository = new Repository<TEntity>(Session);
        _repositories.Add(typeof(TEntity), repository);
        return repository;
    }

    public ITransaction BeginTransaction()
    {
        if(_transaction != null)
        {
            throw new InvalidOperationException("Cannot have more than one transaction per session.");
        }
        _transaction = Session.BeginTransaction(IsolationLevel.ReadCommitted);
        return _transaction;
    }

    public void Commit()
    {
        if(!_transaction.IsActive)
        {
            throw new InvalidOperationException("Cannot commit to inactive transaction.");
        }
        _transaction.Commit();
    }

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

    public void Dispose()
    {
        if(Session != null)
        {
            Session.Dispose();
        }
        if(_transaction != null)
        {
            _transaction.Dispose();
        }
    }
}

IReadRepository

public interface IReadRepository<TEntity> where TEntity : class
{
    IQueryable<TEntity> All();
    TEntity FindBy(Expression<Func<TEntity, bool>> expression);
    TEntity FindBy(object id);
    IQueryable<TEntity> FilterBy(Expression<Func<TEntity, bool>> expression);
}

IWriteRepository

public interface IWriteRepository<TEntity> where TEntity : class
{
    bool Add(TEntity entity);
    bool Add(IEnumerable<TEntity> entities);
    bool Update(TEntity entity);
    bool Update(IEnumerable<TEntity> entities);
    bool Delete(TEntity entity);
    bool Delete(IEnumerable<TEntity> entities);
}

IReadWriteRepository

public interface IReadWriteRepository<TEntity> : IReadRepository<TEntity>, IWriteRepository<TEntity> 
    where TEntity : class { }

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
}

The code is untested, however, it should work. I'm open to as many suggestions as you can dish out.

\$\endgroup\$
  • \$\begingroup\$ I don't see any benefit to having the ReadRepository and WriteRepository properties on an IReadWriteRepository. \$\endgroup\$ – craftworkgames Jul 1 '14 at 3:58
  • \$\begingroup\$ @craftworkgames You know, that's a good question. My thought process was, "What if I need to enforce having both read and write?" But it felt strange leaving the interface empty. \$\endgroup\$ – Charles W Jul 1 '14 at 12:24
  • 1
    \$\begingroup\$ There's nothing wrong with an empty interface in this case because the interface aggregates other interfaces together. What you've done is mixed the "has a" and "is a" relationship, which is odd. \$\endgroup\$ – craftworkgames Jul 2 '14 at 2:52
  • \$\begingroup\$ Thanks for the explanation. I'll make an edit next time I'm at my desk. :) \$\endgroup\$ – Charles W Jul 2 '14 at 12:31
4
\$\begingroup\$

Is there a reason why others instantiate their transaction per unit of work?

Yes, this is because more often than not, a unit of work is a single transaction, either it's committed, or it's not.

Committing part of the work and not other parts, is not really a single unit.

Additionally, Unit of Work is used frequently in web development where one unit = one http request, many people inject a unit of work into the http context in some way to allow a single post/get to commit or fail.

Also, looking at your code, you only use one repository per unit of work, this doesn't seem quite right, what if you wanted to do more than one thing / operate on more than one type?

The unit of work is supposed to wrap up several actions (such as several repository operations) and then either commit all or rollback. Your current implementation doesn't seem to offer you that particularly.

\$\endgroup\$
  • 2
    \$\begingroup\$ Single repository? My UnitOfWork class has a GetRepository<TEntity> method that returns a repository of type TEntity. It then stores that repository in the private _repositories dictionary for later lookup. \$\endgroup\$ – Charles W Jun 25 '14 at 15:07
  • \$\begingroup\$ My apologies, missed that. I'm not sure unless I'm missing something that the IoC concept is being used, I think your UoW now needs to have many 'GetRepository' calls and has no effective way of understanding it's dependencies. I could be missing something, but could you explain how you'd plan to use the UoW and several repositories in a Business Logic class? I'm picturing you needing to inject a UoW in the constructor, but then the UoW needing many GetRepository calls, presumably in the ctor. How are you defining the repo's you need in the BL? How are you subbing in mocks/stubs when testing? \$\endgroup\$ – dougajmcdonald Jun 26 '14 at 21:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.