0

I'm trying to use unit of work and repository in my project. Which then got me thinking on how to implement transaction with it.

At the moment this is what I plan to do :

public class UnitOfWork : IUnitOfWork
{

    private readonly IDbFactory databaseFactory;
    private adminBoContext dataContext;
    private DbContextTransaction _transaction;

    private Repository<a> repoA;
    ....
    private Repository<x> repoX;

    public UnitOfWork(IDbFactory databaseFactory)
    {
        this.databaseFactory = databaseFactory;
        _transaction = dataContext.Database.BeginTransaction();
    }

    protected Context DataContext
    {
        get { return dataContext ?? (dataContext = databaseFactory.Get()); }
    }

    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        catch (Exception ex)
        {
            _transaction.Rollback();
        }
    }
}

but then I also come across example such as SocialGoal project where it has Unit of works (but without repositories inside it) and separate repositories that looks like has its own instance of the context

public class UnitOfWork : IUnitOfWork
{
    private readonly IDatabaseFactory databaseFactory;
    private SocialGoalEntities dataContext;

    public UnitOfWork(IDatabaseFactory databaseFactory)
    {
        this.databaseFactory = databaseFactory;
    }

    protected SocialGoalEntities DataContext
    {
        get { return dataContext ?? (dataContext = databaseFactory.Get()); }
    }

    public void Commit()
    {
        DataContext.Commit();
    }
}

public abstract class RepositoryBase<T> where T : class
{
    private SocialGoalEntities dataContext;
    private readonly IDbSet<T> dbset;
    protected RepositoryBase(IDatabaseFactory databaseFactory)
    {
        DatabaseFactory = databaseFactory;
        dbset = DataContext.Set<T>();
    }

    protected IDatabaseFactory DatabaseFactory
    {
        get;
        private set;
    }

    protected SocialGoalEntities DataContext
    {
        get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
    }
    public virtual void Add(T entity)
    {
        dbset.Add(entity);
    }

I'm really confuse now at how actually unit of work and repository should be implemented, because as I understand that unit of work will be kind of the main gate which all the access to repositories will pass through.

I would very appreciate it if someone could shed some light on this unit of work and repository implementation.

Thank you so much.

hollycrab
  • 315
  • 3
  • 13

3 Answers3

3

UnitOfWork should start and finish the transaction. This is directly derived from its name.

What I would change in your original code is the fact that your UoW knows too much of your repositories. If you add one more repository, you have to change your UoW class and it is a Bad Thing.

You need to inject your UoW instance into the generic repository constructor instead and use the UoW context inside the repository.

When using IoC containers, you need to ensure that you have matching lifetime scope for your repositories and for the UoW.

Alexey Zimarev
  • 17,944
  • 2
  • 55
  • 83
1

The dataContext should be created only in your Unit Of Work class and pass the same dataContext into your repositories, that dataContext should also be committed or rolled back in your Unit Of Work.

Your repositories should not create its own dataContext but use the passed in dataContext from the Unit of work. With that, we can coordinate transaction across repositories.

Example code from your question could be implemented like this:

public class UnitOfWork : IUnitOfWork
{

    private readonly IDbFactory databaseFactory;
    private adminBoContext dataContext;
    private DbContextTransaction _transaction;

    private Repository<a> repoA;
    private Repository<x> repoX;

    public UnitOfWork(IDbFactory databaseFactory)
    {
        this.databaseFactory = databaseFactory;
        _transaction = DataContext.Database.BeginTransaction();

        //pass the same context to your repositories
        repoA = new Repository<A>(DataContext); 
        repoX = new Repository<X>(DataContext); 
    }

    protected Context DataContext
    {
        get { return dataContext ?? (dataContext = databaseFactory.Get()); }
    }

    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        catch (Exception ex)
        {
            _transaction.Rollback();
        }
    }
}

Side note:

Your Unit of work should implement IDisposable to ensure that the context is always disposed even when we forget to do it explicitly. Implement some more code like this:

   private bool disposed = false;

   protected virtual void Dispose(bool disposing)
   {
        if (!this.disposed)
        {
            if (disposing)
            {
                dataContext.Dispose();
            }
        }
        this.disposed = true;
   }

   public void Dispose()
   {
        Dispose(true);
        GC.SuppressFinalize(this);
   }

Your code should look similar to:

public class UnitOfWork : IUnitOfWork, IDisposable
    {

        private readonly IDbFactory databaseFactory;
        private adminBoContext dataContext;
        private DbContextTransaction _transaction;

        private Repository<a> repoA;
        private Repository<x> repoX;

        public UnitOfWork(IDbFactory databaseFactory)
        {
            this.databaseFactory = databaseFactory;
            _transaction = DataContext.Database.BeginTransaction();

            //pass the same context to your repositories
            repoA = new Repository<A>(DataContext); 
            repoX = new Repository<X>(DataContext); 
        }

        protected Context DataContext
        {
            get { return dataContext ?? (dataContext = databaseFactory.Get()); }
        }

        public void Commit()
        {
            try
            {
                _transaction.Commit();
            }
            catch (Exception ex)
            {
                _transaction.Rollback();
            }
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    dataContext.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

Your repository:

public abstract class RepositoryBase<T> : IRepository<T>
    where T : class 
{
    protected DbSet<T> Set { get; set; }
    protected Context Context { get; private set; }

    protected RepositoryBase(Context dataContext)
    {
        Context = dataContext;
        Set = Context.Set<T>();          
    }
Khanh TO
  • 48,509
  • 13
  • 99
  • 115
0

Thanks Alexey and Khanh,

Following your suggestion, I've try to modify my unit of work as below :

public class UnitOfWork : IUnitOfWork
{
    private readonly IDbFactory databaseFactory;
    private Context dataContext;
    private DbContextTransaction _transaction;

    public UnitOfWork(IDbFactory databaseFactory)
    {
        this.databaseFactory = databaseFactory;

    }

    public IDbContext BeginTransaction<T>()
    {
        if (_dataContext == null)
        {
            _dataContext = _databaseFactory.Get<T>();
            _transaction = _dataContext.BeginTransaction();
        }

        return _dataContext;
    }

    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        catch (Exception ex)
        {
            _transaction.Rollback();
            throw ex;
        }
    }
}

and I take unit of work when instantiate the repository as follow :

public abstract class RepositoryBase<T> : IRepository<T>
    where T : class 
{
    protected DbSet<T> Set { get; set; }
    protected Context Context { get; private set; }

    protected RepositoryBase(IDbContext context)
    {
        Context = context;
        Set = Context.Set<T>();             
    }

    //CRUD functions

    public void Save()
    {
       Context.SaveChanges();
    }
 }

@Alexey, is this what you mean by injecting UOW into repository ?

@Khanh, now I'm passing the context directly into the repository and add Save method into it, so it can save (for case when I need newly inserted entity) but then still wrap inside the transaction.

Thank you

hollycrab
  • 315
  • 3
  • 13
  • Check out my updated answer. Personally, I don't like passing the entire unit of work to the repository, we should pass `only what is needed` which is the DataContext. Hope it helps. – Khanh TO Mar 13 '16 at 11:39
  • Thanks Khanh, I edit my code above with passing directly the context into repository and also add a save method for it. – hollycrab Mar 17 '16 at 00:26
  • you should not call save inside the repository, let the unit of work do it. That's why it's called unit of work (even that unit of work has only 1 operation) – Khanh TO Mar 17 '16 at 12:19
  • if I dont call save inside the repository, how should I handle a case where I need to insert related records which for example need the id of the first record ? – hollycrab Mar 18 '16 at 02:16
  • It's better to use application generated identity than database generated identity (something like Guid as Id) so that we don't need to depend on the db and avoid roundtrip to the db just to generate the Id. In case you're working with a legacy db, you could use `SaveChanges` in repo as a workaround in those case. But looks like, in EF we don't need to do so http://stackoverflow.com/questions/11413537/why-does-identity-generator-break-unit-of-work-in-nhibernate-what-about-ef – Khanh TO Mar 19 '16 at 03:51