Unit of Work pattern

 

Most articles about Unit of Work (UoW) pattern expose good theory and incorrect implementations. Here, I want to focus on common mistakes when implementing this pattern with .NET, Entity Framework (EF) library and DDD principles.

But before start, I want to highlight the spirit of this pattern: Give to a program the capacity to persisit some objects changes all together or none. For a full description of this pattern, see P of EAA page 184.

With some minor variations, most articles about UoW suggest an interface like this one:

public interface IUnitOfWork: IDisposable
{
  void Commit();
  void Rollback();
  IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;
}

What could be wrong with it? The generic repository interface (IRepository<T>) antipattern deserves it own article, so here I will focus only on the expected use cases that this interface proposes.

Mistake 1: Coupling all repositories with all UoW clients

Using previous interface, if a class needs to access a repository, we must inject IUnitOfWork and call GetRepository. Any class with a valid IUnitOfWork reference could potentially access any repository, so all repositories become coupled to all IUnitOfWork clients. Some software design principles are broken with this.

If you are tempted to think something like “well, have potential access is not the same than do access…”, please, do a break here and read about Service Locator antipattern. We can describe this IUnitOfWork version as a repository locator.

Some authors try to solve this problem being a little more explicit, like in this version:

public interface IUnitOfWork: IDisposable
{
  void Commit();
  void Rollback();
  IRepository1 GetRepository1();
  IRepository2 GetRepository2();
}

The problem with this approach is that it promotes to have one UoW implementation per program use case. Those implementations do not give any additional value compared to inject directly IRepository1 and IRepository2 into a class that needs them.

Mistake 2: Reinventing the wheel

When dealing with persistence in .NET using EF, UoW concept is already implemented by DbContext class, and when we setup EF, creation and disposing DbContext instances are well configured by us. It is not expected we manually dispose DbContext instances, so there is no point to declare IUnitOfWork as IDisposable. Additionally, if we do not explicitly instruct DbContext to save changes, by default, when it is disposed, all changes are discarded, so there is no point to declare method Rollback.

What about method GetRepository? We do not need it. If we want a service class to have access to a repository, we can (must) inject a valid reference to its interface. In that way, all really used references are declared explicitly, and we only couple what it needs to be coupled.

So finally, we end up with something like this:

public interface IUnitOfWork
{
  void Commit();
}

And this is all we need. We can inject it in any application layer service where we want to control persistency, together with all repositories we want to work with. Note this IUnitOfWork version does not represent a full UoW functionality, only the commit part. Better names could be IUnitOfWorkCommitter, IDatabaseCommitter, etc.

Mistake 3: Invert dependencies between UoW and repositories

When using UoW and repository patterns together, the dependency direction in relevant: Repositories are independent, that is, they do not need to know if a UoW object exist or not. Some implementations invert this dependency, like this one:

public interface IRespository1
{
  // other methods

  IUnitOfWork GetUnitOfWork();
}

Maybe this is because some widely used documentations present UoW with inconsistent diagrams, like in this one from Microsoft:

Diagram described as: The relationship between repositories, aggregates, and database tables.

With inverted direction we not only affect the S in SOLID, but also reading the resulting code is more difficult, like in this fragment:

var repo1 = GetRepo1();
var repo2 = GetRepo2();
repo1.Add(obj1);
repo2.Remove(obj2);
repo2.GetUnitOfWork().SaveChanges();

Why programmer only save changes related to repo2? It is easier to read, to implement and to maintain something like:

uow.GetRepo1().Add(obj1);
uow.GetRepo2().Remove(obj2);
uow.SaveChanges();

Or even better, if we apply what I suggest in mistake 2 and repositories and UoW committer are injected separately:

repo1.Add(obj1);
repo2.Remove(obj2);
uow.SaveChanges();

Mistake 4: Always work with repositories

In book Patterns of Enterprise Application Architecture, UoW is described without any reference to repositories. You can work with UoW only, with repositories only or with both together. The reason can be better understood by looking at this diagram from Microsoft documentation:

Relationship between UoW, repositories and other elements in an application.

As we can see in this diagram, data access layer clients (controllers in this case) first reference is UoW, not repositories. We can implement UoW so they hide completely repositories or decide to expose them. Even more, if most of your persistence logic complexity falls into properly coordinates changes of objects of multiple datasets, working only with UoW could have more sense. And if logic related to specific datasets grows, you can move it to private data layer repositories, so only UoW implementations use them.

Final comments

Do we really need to implement something like IUnitOfWork? In simple cases where all work unity you need can be handle by individual repositories, these repositories can be UoW and implement directly SaveChange methods.

But what about not simple cases, when your unity needs embraces more then one repository. UoW is tight related to transaction concept, and transactions goes beyond database persistency (EF). In .NET ecosystem, System.Transactions namespace has a long existence, and concepts like ambient transactions and its implementation (TransactionScope class) are also supported by EF. Unfortunately, types in System.Transactions have not received enough work last years, so they still have some important limitations to be properly adopted in modern programs, like full async/await support and distributed transactions support outside Windows operative systems. If we feel comfortable with those limitations then we can use ambient transactions to avoid completely implement UoW concept by ourself.

There is an additional point of view related to UoW need. Because EF already implement UoW concept in DbContext class and repository concept in DbSet class, many programmers have strong opinions not only against implement UoW by ourselves, but also implement specific repository classes. They encourage to use directly DbContext derived classes and DbSet along application layer. I agree with those opinions only for extremely simple programs. When complexity grows, mix in the same method business logic with data access logic (for example, a LINQ expression) starts breaking the S in SOLID.

By Deesha

Leave a Reply

Your email address will not be published. Required fields are marked *