Combining Unit of Work
and Repository Pattern
is something used fairly widely nowadays. As Martin Fowler says a purpose of using UoW
is to form a Business Transaction while being ignorant of how repositories actually work (being persistent ignorant). I've reviewed many implementations; and ignoring specific details (concrete/abstract class, interface,...) they are more or less similar to what follows:
public class RepositoryBase<T>
{
private UoW _uow;
public RepositoryBase(UoW uow) // injecting UoW instance via constructor
{
_uow = uow;
}
public void Add(T entity)
{
// Add logic here
}
// +other CRUD methods
}
public class UoW
{
// Holding one repository per domain entity
public RepositoryBase<Order> OrderRep { get; set; }
public RepositoryBase<Customer> CustomerRep { get; set; }
// +other repositories
public void Commit()
{
// Psedudo code:
For all the contained repositories do:
store repository changes.
}
}
Now my problem:
UoW
exposes public method Commit
to store the changes. Also, because each repository has a shared instance of UoW
, each Repository
can access method Commit
on UoW. Calling it by one repository makes all other repositories store their changes too; hence the result the whole concept of transaction collapses:
class Repository<T> : RepositoryBase<T>
{
private UoW _uow;
public void SomeMethod()
{
// some processing or data manipulations here
_uow.Commit(); // makes other repositories also save their changes
}
}
I think this must be not allowed. Considering the purpose of the UoW
(business transaction), the method Commit
should be exposed only to the one who started a Business Transaction for example Business Layer. What surprised me is that I couldn't find any article addressing this issue. In all of them Commit
can be called by any repo being injected.
PS: I know I can tell my developers not to call Commit
in a Repository
but a trusted Architecture is more reliable than trusted developers!
I do agree with your concerns. I prefer to have an ambient unit of work, where the outermost function opening a unit of work is the one that decides whether to commit or abort. Functions called can open a unit of work scope which automatically enlists in the ambient UoW if there is one, or creates a new one if there is none.
The implementation of the UnitOfWorkScope
that I used is heavily inspired by how TransactionScope
works. Using an ambient/scoped approach also removes the need for dependency injection.
A method that performs a query looks like this:
public static Entities.Car GetCar(int id)
{
using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Reading))
{
return uow.DbContext.Cars.Single(c => c.CarId == id);
}
}
A method that writes looks like this:
using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Writing))
{
Car c = SharedQueries.GetCar(carId);
c.Color = "White";
uow.SaveChanges();
}
Note that the uow.SaveChanges()
call will only do an actual save to the database if this is the root (otermost) scope. Otherwise it is interpreted as an "okay vote" that the root scope will be allowed to save the changes.
The entire implementation of the UnitOfWorkScope
is available at: http://coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/