Call different methods, from different repositories, in same transaction using Unit of work

2.1k views Asked by At

I am still learning UnitOfWork pattern and I am not comfortable with this yet. I found many examples but nothing is clear enough for my problem. I want use UnitOfWork with Ado.Net. I have many repositories. I want call different methods from different repositories in same transaction using Unit of work.

For example a have this 2 repositories.

public class FirstRepository : IFirstRepository
    {
        private readonly ILogger logger;
        private readonly IImportConfiguration configuration;

        public FirstRepository(ILogger logger, IImportConfiguration configuration)
        {
            this.logger = logger;
            this.configuration = configuration;
        }   
        public int Save()
        {
            //Save to DB with Ado.Net
            return 1;
        }
    }

public class SecondRepository : ISecondRepository
    {
        private readonly ILogger logger;
        private readonly IImportConfiguration configuration;

        public SecondRepository(ILogger logger, IImportConfiguration configuration)
        {
            this.logger = logger;
            this.configuration = configuration;
        }

        public int Update()
        {
            //Update in DB with Ado.Net
            return 1;
        }
    }

I want call functions Save() and Update() in same transaction.

using (var uow = UnitOfWorkFactory.Create())
{
     firstRepository.Save(); 
     secondRepository.Update();

     _unitOfWork.SaveChanges();
}

Problem is how to use same UnitOfWork in both repositories ? Only thing I can see is add additional parameter to functions

//in first repository
Save(IUnitOfWork uow)

//in second repository
Update(IUnitOfWork uow)
//****************************
using (var uow = UnitOfWorkFactory.Create())
{
     firstRepository.Save(uow); 
     secondRepository.Update(uow);

     _unitOfWork.SaveChanges();
}

This is ugly solution, because i must have this parameter in all functions that work with DB. I am using Dependency injection. ILogger and IImportConfiguration are injected with AutoFac. Maybe would be good to register all repositories in UnitOfWork? But how to do that? I cant have just one instance injected in all repositories. Any idea?

1

There are 1 answers

0
Callum Linington On BEST ANSWER
public class UnitOfWork
{
    public DbSet<Company> Companies { get; set; }

    public int SaveChanges()
    {
        underlyingContext.SaveChanges();
    }
}

public class UnitOfWorkFactory
{
    public UnitOfWork Create() 
    {
        // real creation logic
        return new UnitOfWork();
    }
}

public class CompanyRepository
{
    private readonly UnitOfWork uow;

    public CompanyRepository(UnitOfWork uow)
    {
        uow = uow;
    }

    public void Add(Company company)
    {
        uow.Companies.Add(company);
    }  
}

public class CompanyRepositoryFactory
{
    public Create(UnitOfWork uow)
    {
        new CompanyRepository(uow);
    }
}

Tying it all together:

var uow = new UnitOfWorkFactory().Create();
var companyRepository = new CompanyRepositoryFactory().Create(uow);

So to use DI, you need to create interfaces for all these.

The unit of work is based round some Data Layer connection, for example EF uses DbContext which you would use in the underlying UnitOfWork class.

Other things you can do is make IUnitOfWork (the interface) inherit IDisposable so you use the using().

To make it so you don't have a hundred repository classes (although not really a bad thing) you can make it Generic, so IRepository<T> and Repository<T>

So for a generic repo and unit of work using EF.

public class UnitOfWork : IUnitOfWork
{
    ProjectDbContext context;

    public UnitOfWork() {
        context = new ProjectDbContext();    
    }

    public IQueryable<T> Query<T>(Expression<Func<bool, t>> predicate)
    {
        return context.Set<T>().Where(predicate);
    }

    public void Add<T>(T entity)
    {
        context.Set<T>().Add(entity);
    }

    public int SaveChanges()
    {
        return context.SaveChanges();
    }

    public void Dispose()
    {
        context.Dispose();
    }
}

public class UnitOfWorkFactory
{
    Lazy<UnitOfWork> lazyUOW = new Lazy<UnitOfWork>(() => new UnitOfWork());

    public UnitOfWork Create() 
    {
        // having the DI initialise as Singleton isn't enough.
        return lazyUOW.Value;
    }
}

public class Repository<T> : IRepository<T>
{
    private readonly IUnitOfWork uow;

    public Repository(IUnitOfWork uow)
    {
        uow = uow;
    }

    public void Add(T entity)
    {
        uow.Add(entity);
    }

    public List<T> AllBySomePredicate(Expression<Func<bool, T>> predicate)
    {
        return uow.Query(predicate).ToList();
    }  
}

public class RepositoryFactory : IRepositoryFactory 
{
    public Create<T>(UnitOfWork uow)
    {
        new Repistory<T>(uow);
    }
}

Usage:

public class CompanyController : Controller
{
     private readonly IUnitOfWorkFactory uowFactory;
     private readonly IRepositoryFactory repoFactory;

     public CompanyController (
             IUnitOfWorkFactory uowFactory,
             IRepositoryFactory repoFactory)
     {
          uowFactory = uowFactory;
          repoFactory = repoFactory;
     }

     public ActionResult Index()
     {
         using(var uow = uowFactory.Create()) 
         {
             var companyRepo = repoFactory.Create<Company>(uow);

             return View(companyRepo.AllBySomePredicate(x => x.CompanyJoined == DateTime.Now.AddMonths(-2)));
         }
     }
}