hollycrab
hollycrab

Reputation: 325

Using Unit of work and repository

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.

Upvotes: 0

Views: 557

Answers (3)

hollycrab
hollycrab

Reputation: 325

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

Upvotes: 0

Khanh TO
Khanh TO

Reputation: 48972

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>();          
    }

Upvotes: 1

Alexey Zimarev
Alexey Zimarev

Reputation: 19610

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.

Upvotes: 3

Related Questions