3
\$\begingroup\$

Searching on Stack Overflow and Google, I find many and many different implementation for Unit-of-Work/Repository patterns, but none of that really convinced me; each had some small defect that prevented me from using it. So I tried to create my own version and would like to know what you think.

Essentially everything is based on the use of interfaces and UnitOfWork pattern built in the style of the Entity Framework, even if I use NHibernate as implementation.

Wanting to have absolutely generic interfaces and can therefore accommodate both Entity Framework and NHibernate, my UoW does not expose any of the specific concepts of the two repositories such as session or transaction.

This is the usage:

using (IUnitOfWork uow = new NHUnitOfWork())
        {
            uow.StartOperation();
            Class1 c1 = _uow.Class1Repo.Read<Class1>(idClasse1);
            c1.Desc = "edited";
            uow.Class1Repo.Update(c1);
        }

Of course the new instance can be avoided using dependency injection.

These are the interfaces:

public interface IUnitOfWork : IDisposable{
    void StartOperation();
    void SaveAll();
    void DiscardChanges();
    void OpenConnection();
    void CloseConenction();

    IClass1Repository Class1Repo { get; }
            // All other repository here
}

At this point I created the basic interface of my generic repository containing all the operations that must be provided (if you want you can create generic repository for distinct divide the reading operations from those of writing so that you can manage the repository with limited permissions for objects that not foresee the change, but I had no need of this type).

public interface IRepoBase<T> : IDisposable where T : class  {
        void Create(T obj);
        T Read<T>(object id);
        void Update(T obj);
        void Delete(T obj);
}

All the remaining read operations of individual objects will be managed in their specific repository.

public interface IClass1Repository : IDisposable, IRepoBase<Class1> 
{
    IEnumerable<Class1> FindAll();
    string GetDescription();
}

All the conditions and filters to queries will be made directly into the repository using more specific methods, why not use it IEnumerable IQueryable. In this way they have no ties to Linq.

public class NHUnitOfWork: IUnitOfWork{
    private ITransaction _transaction;
    private NHSessionFactory _nhHelper;
    private ISession _session;
    public IClass1Repository Class1Repo { get; private set; }

    public NHUnitOfWork(NHSessionFactory nhHelper)        {
        _nhHelper = nhHelper ?? new NHSessionFactory();
        InitializeSession();        }

    public NHUnitOfWork()        {
        _nhHelper = new NHSessionFactory();
        InitializeSession();        }

    private void InitializeSession()        {
        if (_session == null)
               _session = _nhHelper.OpenSession();
        Class1Repo = new Class1Repository(this._session);
                    // All other repository here    }

    public void StartOperation()    {
        InitializeSession();
        _transaction = _session.BeginTransaction();    }

    public void SaveAll()    {            
        if (_transaction != null)        {
            _transaction.Commit();
            _transaction = null;    }    }

    public void DiscardChanges()    {
        Contract.Ensures(_session.Transaction.IsActive == false);
        if (_transaction != null)        {
            _transaction.Rollback();
            _transaction = null;    }    }


    public void OpenConnection()        {
        InitializeSession();        }

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

        if (_session != null)    {
            _session.Dispose();
            _session = null;    }
    }

    // Disposable pattern here
}

OpenConnection and CloseConenction hide the NHibernate session, while StartOperation, SaveAll and DiscardChanges hide the concept of atomic operation (transaction).

NHibernate implementation of BaseRepository:

class NHRepoBase<T> : IDisposable,  IRepoBase<T> where T : class
{
    protected ISession _session;
    public NHRepoBase(ISession session)
    {_session = session;    }

    public void Create(T obj)
    { _session.SaveOrUpdate(obj); }

    public void Update(T obj)
    { _session.Merge<T>(obj); }

    public void Delete(T obj)
    { _session.Delete(obj); }

    public T Read<T>(object id)
    { return _session.Get<T>(id); }

    private bool disposed = false;
    protected virtual void Dispose(bool disposing) {
        if (!this.disposed){
            if (disposing)
             _session.Dispose();                
        }
        this.disposed = true;
    }
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

A simple specific repository:

class Class1Repository : NHRepoBase<Class1>, IClass1Repository, IDisposable {
    public Class1Repository(ISession session) : base(session) { }

    public IEnumerable<Class1> FindAll()
    { return _session.QueryOver<Class1>().List();     }

    public string GetDescription()
    { return _session.QueryOver<Class1>().Select(c => c.Desc).SingleOrDefault<string>(); }
}
\$\endgroup\$
6
  • \$\begingroup\$ Is there ever a reason to not call StartOperation? Don't create objects in an invalid state. Call it from the constructor. \$\endgroup\$ Commented Jun 25, 2016 at 11:07
  • \$\begingroup\$ it's for doing many operation with same unit of work \$\endgroup\$ Commented Jun 27, 2016 at 6:50
  • \$\begingroup\$ Why would you want to reuse the same instance for that purpose? The closest thing to stop operation is dispose so you shouldn't have a start operation method. \$\endgroup\$ Commented Jun 27, 2016 at 9:43
  • \$\begingroup\$ so you mean that a UnitOfWork must have only one session and one transaction? if i close that i have to create a new Uow? \$\endgroup\$ Commented Jun 27, 2016 at 11:53
  • \$\begingroup\$ That is what I would expect from a disposable type, and also from the notion of unit of work. Then again I come from the EF world not the Nhibernate world \$\endgroup\$ Commented Jun 27, 2016 at 11:55

2 Answers 2

2
\$\begingroup\$

Unit of work

According to Martin Fowler:

A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.

EF does that for you, and I guess NH does that as well. In the end you are abstracting away the call to SaveChanges().

But if you need something on top of these, I would suggest a few changes.

Simpler usage

  • When I create a new UoW, I don't want to decide which ORM to use.
  • When I add a new repository, I don't want to update IUnitOfWork and all its implementations.
  • Some methods are implied by the usage: I don't create a UoW unless I want to start an operation. Discard should happen automatically in case of an error. No errors means I want to save everything.

Simplified usage:

using (IUnitOfWork uow = new UnitOfWork())
{
    // do stuff
    uow.SaveAll();
}

Decouple UoW and repositories

Adding a new repository should be as simple as adding one new class and we're done:

using (IUnitOfWork uow = new UnitOfWork())
{
    var repository = new GizmoRepository(uow);
    var gizmo = repository.Read(id);
    // do stuff
    uow.SaveAll();
}

The repository should decide which ORM to use:

public class GizmoRepository
{
    public GizmoRepository(IOrmProvider provider)
    {
        _session = provider.GetOrm<ISession>();
    }
}

An EF based repository would ask for a DbContext:

public class GadgetRepository
{
    public GadgetRepository(IOrmProvider provider)
    {
        context = provider.GetOrm<YourDbContext>();
    }
}

UoW need to implement more interfaces:

public class UnitOfWork : IUnitOfWork, IProvideOrm, IDisposable
{
    public T GetOrm<T>()
    {
        // returns the same instance for every supported ORM
    }

    public void SaveAll()
    {
        // flags if changes so far should be saved
    }

    public void Dispose()
    {
        // if SaveAll() was called, save all changes in all related ORMs,
        // otherwise, discard or rollback all changes
    }
}

Benefits

  • Simpler usage.
  • It's simpler to add new repositories.
  • Each repository use the ORM directly. All features are available and there's no need to add persistence-related interface methods.
\$\endgroup\$
8
  • \$\begingroup\$ avoid altering UOW when create new repo is interesting but i don't want to have directly access to repository object, one of my first goal is to find them all in my UOW without having to guess their existence. some more time spend in definition but in this way i can avoid to create new repo object every time i need one. iam interesting in how you manage session start and end since you have no reference to NHIbernate or EF in your UOW \$\endgroup\$ Commented Dec 28, 2015 at 15:00
  • \$\begingroup\$ There are no references to EF/NH simply because I didn't care to write the implementation in this example. I'm thinking that a field for every ORM will do. Or a dictionary with Type as key. Then GetOrm<T>() will create an instance if it doesn't exist, otherwise return the existing ones. A session starts with the first call to GetOrm<T>() and ends with a call to Dispose() \$\endgroup\$ Commented Dec 28, 2015 at 21:52
  • \$\begingroup\$ You decide, of course, it's your code. Personally, I don't see the problem with "guessing". How many repositories can there be? \$\endgroup\$ Commented Dec 28, 2015 at 21:59
  • \$\begingroup\$ on actual project we have 240 object and 89 repository... \$\endgroup\$ Commented Dec 29, 2015 at 8:24
  • \$\begingroup\$ But doesn't that mean you have to implement 89 properties on each implementation of IUnitOfWork? That's a lot of work. If one repository only supports EF, what do you do with the property on NHUnitOfWork? How should the developer know which implementation of IUnitOfWork to use? Why should he know in the first place? And why do you want to couple a single interface to 89 different types? Let's say you want to support yet another ORM. Or you need to mock it in a unit test. Now you have 80+ properties you don't care about, but have to implement. Is this really what you want? \$\endgroup\$ Commented Jan 1, 2016 at 3:33
0
\$\begingroup\$

Trying to abstract away the the specifics of the data provider may be helpful, considering they use different concurrency models it's probably not that simple. For example neither of you are using any async methods and when you start doing that you'll have to be more concerned with the underlying orm. Anyway if you're trying to be framework agnostic you should probably wrap the object exposed to the repositories in an interface.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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