A more abstract Unit of Work in C#

Billy Vlachos
5 min readApr 19, 2021

A unit of work is a concept that is related to an effective implementation of the well known Repository Pattern. In order to begin to understand what a Unit of Work actually is, we first need to understand the Repository Pattern.

In this guide, we will create a more abstract implementation of the Unit of Work Pattern working along with the Repository Pattern in C#.

Repository Pattern

The Repository Pattern is a well-documented way of working with a data source. A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory.

It is used to decouple the business logic and the data access layers in your application. The data access layer typically contains storage specific code and methods to operate on the data to and from the data storage. In the Repository Pattern, the domain entities, the data access logic and the business logic talk to each other using interfaces.

In this example, we will begin by implementing an abstracted repository, by starting with a very basic model definition.

This line allows us to define how every entity referenced by a repository is going to look like. At this point, we can go ahead an implement an abstract class for Entity that inherits from our newly created IEntity interface.

All future aggregate models used by our repositories will inherit from this base class. In order to be able to define a repository, we need to come up with a basic interface which does not contain any generics.

As you can see, it does not contain any definitions, and that is ok for now. Next, we will create the main IRepository which will be implementing a few methods using a generic and derive from IRepositoryBase.

This next step involves the first implementation of a very basic repository. But in order to start designing it, we first need to define a base Context which will be used to retrieve our entities. In this example we will be using the NuGet package Microsoft.EntityFrameworkCore and start by designing a Context interface that will serve as our reference Context.

Now comes the fun part, we will begin constructing our abstract repository. This will be an abstract class that contains a generic TEntity and inherits from our IRepository interface.

You will notice here that we did not implement a Save method, and there is a very good reason for that. We will cover this in the following section when we will talk about the Unit of Work Pattern.

In addition, notice how both Context and Set are both set protected. The reason for that is so that we can use both of them in our derived repositories to construct additional logic for accessing our data.

Now that we covered a very basic implementation of the Repository Patter, let’s move on to implementing our Unit of Work.

Unit of Work Pattern

We will start by defining an interface for our base unit of work. This will be a very simple interface with only one async method called CommitAsync() which will enable us to commit any changes made in the Unit of Work.

At this point we are ready to implement an abstract class for our Unit of Work.

In the above example, we are defining an abstract class called UnitOfWorkBase with one generic argument TContext which is constrained to inherit from IContext. The constructor accepts the context as an argument and assigns it to a private read only field. As you may have noticed, the commit command is actually saving changes to the entire context, as this will be shared with our repositories contained inside this Unit of Work.

Typically, in many example online, you will find units of work that inherit from a base abstract class such as the one above. They then contain properties for each of the repositories used and exposed as their interface counterpart, such as in the example below:

In this example implementation of the unit of work, we can see how in the derived unit of work we are actually exposing one of our repositories by first checking if it has already been initialized then returning a new one after assigning it to a private field. This example works perfectly, but requires a little bit of coding.

There is a better way to implement this and it involves mapping each interface to its counterpart concrete repository and initializing it when needed then storing it inside a private list of initialized repositories. I will edit the UnitOfWorkBase class to allow for this.

We will first create a private dictionary that will hold the mappings between our interface and concrete repositories:

private readonly Dictionary<Type, Type> _repositoryMappings = new Dictionary<Type, Type>();

In order to add the mappings into this dictionary, we will need another generic protected method called AddMappings. This method accepts two generic parameters, one for the interface and the other for the concrete implementation of that interface.

protected void AddMapping<TInterface, TRepository>()
where TInterface : IRepositoryBase
where TRepository : class, IRepositoryBase
{
var isInterfaceType = typeof(TRepository).GetInterfaces().Any(x =>
x.IsGenericType && x.GetGenericTypeDefinition() == typeof(TInterface));
if (!isInterfaceType)
throw new Exception($"The {nameof(TRepository)} does not implement interface {nameof(TInterface)}");
_repositoryMappings.Add(typeof(TInterface), typeof(TRepository));
}

The method above performs a quick check to see if the repository provided actually implements the interface provided and throws an exception if it doesn’t.

Then we will create another dictionary that will contain the instantiated repositories using their interface counterpart as the key:

private readonly Dictionary<Type, IBaseRepository> _initializedRepositories = new Dictionary<Type, IBaseRepository>();

Note how this dictionary will hold an object that inherits from the IBaseRepository which is the very base interface of all the derived repositories. The key is the Type of the repository interface we want to instantiate.

Following this, we will create our final generic method called Get.

protected TRepository Get<TRepository>()
where TRepository : IRepositoryBase
{
var type = typeof(TRepository);
try
{
if (_instantiatedRepositories[type] is { } repository)
return (TRepository)repository;
}
catch (KeyNotFoundException)
{ }
var repoType = _repositoryMappings[type];
var initializedRepository =
(TRepository)Activator.CreateInstance(repoType, Context);
_instantiatedRepositories.Add(typeof(TRepository),
instantiatedRepository);
return instantiatedRepository;
}

Now this method is where the magic happens. First we get the type of the interface repository from the generic parameter. Using this type, we attempt to retrieve an instantiated repository from our dictionary. If none exists, that means that it has not been instantiated yet, therefore we continue with retrieving the type of the concrete repository and using reflections attempt to instantiated with one parameter, that of Context.

Once instantiated, we add it to our instantiated repositories list and return it. Our full UnitOfWorkBase class now should now look like this:

Our example implementation now will look a little different:

Usage

Every service in your source can now use this Unit of Work to perform operations on the database using a very abstract layer. You can add the entire unit of work in your DI like so:

services.AddTransient<IUnitOfWork, UnitOfWork>();

And use it in your services using DI:

Of course, by adding more repositories in your unit of work you can essentially perform multiple operations and commit your changes all at once using one call await _uwo.CommitAsync();

Conclusion

Unit of Work in combination with Repository Patter allow us to have single transactions that involves multiple operations of insert/delete/update or more. That means that all transactions are performed in one single transaction, rather than doing multiple database transactions. It also means that our dependent services only need one injection that will contain all underlying repositories, but only the ones actually needed will be instantiated.

Source Code: https://github.com/lakylekidd/unit-of-work-pattern

Happy coding ;)

--

--

Billy Vlachos

Senior software engineer at Vision Healthcare based in Haarlem, Netherlands and living in Amsterdam.