0

I have the following unit of work pattern set up for an MVC 5 application using Entity Framework. The unit of work has all the repos defined as follows so that they are all using the same dbcontext and it has one save method to co-ordinate the transaction using the same context:

public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;

    public IProductRepository ProductRepository { get; private set; }
    public ICustomerRepository CustomerRepository { get; private set; }
    // Other reposistories

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
        ProductRepository = new ProductRepository(_context);
        CustomerRepository = new CustomerRepository(_context);

        // Other reposistories
    }

    public void Complete()
    {
        _context.SaveChanges();
    }
}

This is an example of my repo. The reason for using repos is for code re-use so that I'm not duplicating queries inside different controllers.

public class ProductRepository : IProductRepository
{
    private readonly ApplicationDbContext _context;

    public ProductRepository(ApplicationDbContext context)
    {
        _context = context;
    }

    public Product GetProduct(int productId)
    {
       return _context.Ticket.SingleOrDefault(p => p.Id == productId);
    }

     public void Add(Product product)
    {
        _context.Product.Add(product);
    }

    // Other methods
}

I inject the unit of work class in my controller as follows using Ninject:

 public class ProductsController : Controller
{

    private readonly IUnitOfWork _unitOfWork;
    private readonly IFileUploadService _FileUploadService;

    public ProductsController(IUnitOfWork unitOfWork, 
            IFileUploadService fileUploadService)
    {
        _unitOfWork = unitOfWork; 
        _FileUploadService = fileUploadService;
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(CreateEditProductViewModel viewModel)
    {
          var product = new Product
          {
              // Do stuff
          };

          _unitOfWork.ProductRepository.Add(product);

          // Call file upload service
          _fileUploadService.Upload();

          _unitOfWork.Complete();

    }

 }

This unit of work set up works fine if all I'm using are repos that are defined in the unit of work class. But now I want to use a service class to process some additional application logic and then the unit of work is committed in the controller action. If I define the class as follows it will be using a different instance of the context, In which case how would you co-ordinate a transaction where the service layers is ending up with a different context?

public class FileUploadService : IFileUploadService
{
    private readonly IUnitOfWork _unitOfWork;

    public FileUploadService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public uploadResult Upload()
    {   
        // Some stuff
         var uploadedFile = new UploadedFile
         {
            //some stuff
         };

         _unitOfWork.UploadedFileRepository.Add(uploadedFile);
    }
 }  

I've done quite a bit of research online and I'm unable to find any resource that provides a practical example to solve this problem. I've read quite a bit of stuff on ditching unit of work and repos and simply using entity frameworks dbset. However as explained above the purpose of using repos is to consolidate queries. My questions is how do I co-ordinate the unit of work with a service class.

I would like the service to use the same context so that it can access the repositories it needs to work with, and let the controller (client code) commit the operation when it see fits.

* UPDATE *

In my DI Container I resolve all interfaces using the following snippet:

    private static IKernel CreateKernel()
    {

            RegisterServices(kernel);

            kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();

            // default binding for everything except unit of work
            kernel.Bind(x => x.FromAssembliesMatching("*")
             .SelectAllClasses()
             .Excluding<UnitOfWork>()
             .BindDefaultInterface());

            return kernel;

    } 

Would adding the line kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope(); ensure that no more than one ApplicationDbContext is created, even if the request ends up hitting multiple controllers or service layers that all require an IUnitOfWork (ApplicationDbContext)?

adam78
  • 9,668
  • 24
  • 96
  • 207
  • Use a IOC container to resolve your IUnitOfWork everywhere. Set the Scope or Lifecyle appropriatly: https://structuremap.github.io/object-lifecycle/supported-lifecycles/ – mxmissile Aug 05 '16 at 19:50
  • @mxmissile How Do I do this with ninject? – adam78 Aug 05 '16 at 20:00
  • http://jasonwatmore.com/post/2015/01/28/Unit-of-Work-Repository-Pattern-in-MVC5-with-Fluent-NHibernate-and-Ninject.aspx Is for NHibernate, but you should get he idea. – mxmissile Aug 05 '16 at 20:05
  • @mxmissile don't understand it - way too complicated. – adam78 Aug 05 '16 at 20:43
  • @mxmissile please see my update. Will adding `kernel.Bind().To().InRequestScope();` resolves my issue i.e if within a request I call another service that also requires a UnitOfWork, does it ensure that anothe dbcontext isn't created? – adam78 Aug 06 '16 at 08:13
  • That is the idea yes! Unfortunately I am not familiar with ninject to be able to answer if your implementation is correct. Tag this Q with ninject. But you have the right idea there. – mxmissile Aug 08 '16 at 13:50

2 Answers2

0

If you are using MVC, then your unit of work is your web request. If I were you I'd ditch the UOW implementation and just make sure you dbcontext is instantiated in the Application_BeginRequest. Then I'd stuff it into the HttpContext for safe keeping. On Application_EndRequest, I dispose of the DbContext.

I would move the save to your repository.

I'd create a [Transaction] attribute that would maintain a TransactionScope something like this:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : ActionFilterAttribute
{
    private TransactionScope Transaction { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Transaction = new TransactionScope( TransactionScopeOption.Required);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Exception == null)
        {
            Transaction.Complete();
            return;
        }

        Transaction.Dispose();
    }
}

You can then just tag your controller methods with [Transaction].

I'm just spitballing here, but I do something similar with NHibernate instead of EF and it works out nicely for me.

Fran
  • 6,440
  • 1
  • 23
  • 35
  • you mention moving the save to my repository, but which repository do I move it to? – adam78 Aug 05 '16 at 20:01
  • I use a generic Repository<> so I've only got 1, but you'd have to do it on all your repos, but that's not going to write to the db because you are sitting in a transaction. – Fran Aug 05 '16 at 20:06
0

The InRequestScope() will create a new instance of the bound type on every new web request, and at the end of that web request, it will Dispose that instance if it is disposable.

I am not sure how are you passing the ApplicationDbContext into your UnitOfWork. I am assuming that you use Ninject for this injection too. Just make sure that you bind your ApplicationDbContext using the InRequestScope()Bind.To().InRequestScope();.

This way, your ApplicationDbContext instance will be created once per request and disposed at the end.

Also, the use of InRequestScope is for types that are disposable, so you can also release resoruces in the Dispose method of your UnitOfWork method too.

Syed Waqas
  • 2,576
  • 4
  • 29
  • 36