In my previous post I have presented a way to separate your data access from the business logic, when a relational database is used. I have shown another implementation of the well-known Repository pattern. Since Martin Fowler described it in his book Patterns of Enterprise Application Architecture it became one of the most implemented pattern in enterprise applications. As it happens with all the design patterns, design principles or any other widely known best practice, we find them in many different implementations, mixed in different ways, but they all have identical things, because they all implement the same solution to a common problem. They all have classes and interfaces named the same. Things like:
ConcurencyException and so on.
Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice
In most of the projects I’ve been involved in, I’ve seen the Repository pattern implemented differently. Tweaked to the context of that project, to the specifics of that team, according with their own style. All different implementations of the same patterns and practices.
Out of the curiosity of how many other blog posts are out there, which show similar implementation with the one I’ve presented, I have googled: “generic repository implementation”. As expected there are quite a few, and as expected they are very similar with mine and with each other. I have randomly picked a few (the first 5 returned by my google search at the time of writing this article) that I will comment in this post.
I like the idea of defining an
IEntity interface which should be implemented by all the entities that your repository can persist. This opens the possibility of writing a more generic code into a default implementation of
Repository<T> class), where you could push all the common code like a generic implementation of
.FindById(int Id). This will only work if you can enforce the convention that all the tables in your database have a surrogate primary key of type integer.
I appreciate a lot the discussions in the comments of the post. I am closer to the point of view of ardalis regarding the use of such abstraction on top of EF. However, I don’t see
IQueriable<T> as a leaky abstraction. In my view it abstracts a query, which may be executed on different query providers, EF being one of them. It also helps a lot when Unit Testing, because I can stub the
IRepository to return
The implementation in this blog post also has the
IEntity, but with a slight variation. It is generic by the type of the primary key. With this, we can have different types for the primary key, but it increases the complexity of writing generic code in a common repository implementation.
I like the
IAuditableEntity, which should be implemented by all entities on which we want to store information like
CreatedDate etc. With this, in a generic implementation of the repository before we save an entity we can check if it implements
IAuditableEntityand if yes, then we can set the correct values in
CreatedData properties and then continue with the save.
The implementation of the
UnitOfWork looks a bit strange. It has a
Commit() function, which should be called when the client wants to persist a set of changes. What I don’t like is that the client service will need to use one instance of a
Repository, another instance of an
UnitOfWork and these instances should be built in such way that they wrap the same
DbContext instance. The way objects are created (Dependency Injection, Service Locator or custom factories), needs to take care of this, and all the client services need to be aware of it and use it consistently. Things may get too complex when a client service will need to use repositories of two different entities. It will have to deal with four dependencies only for this.
This implementation goes even further and defines a generic service for maintaining business entities. It is called
EntityService. A suggestive name I’ve also used in different projects, for a similar generic service with the same scope. These set of services should use the repository (data access), encapsulate the logic around maintaining a business entity (business logic) and give to the controller (UI) functions for the basic CRUD operations.
This blog post addresses the
UnitOfWork implementation in a better way than the previous two. Here, the
UnitOfWork has set of
Repository instances that it creates and makes them available through get properties. This solves in a better way the thing that I didn’t like in the previous post. The
DbContext is created and owned by the
UnitOfWork, and because it is the one that creates the repositories it may pass it to the
The drawback of this is that for a client class is quite difficult to know which are the repositories that are available on a specific
UnitOfWork instance. It does not know what get properties to expect from an
UnitOfWork. This may lead to inconsistency. As a client I might end up reaching the entities I want through strange paths starting from different repositories. It may also lead developers to add repository properties to
UnitOfWork classes as they need them in different context, making the
UnitOfWork classes to get fat and difficult to maintain.
UnitOfWork class presented in the article helps, but even that may become quite complex when there are many specific repository implementations that need to be created and cached in the dictionary. Mainly the
UnitOfWork turns into a Service Locator implementation for repositories of different entity types.
This implementation has the same way of implementing a
UnitOfWork as the previous. The
UnitOfWork has a generic repository through a get property.
The article shows the idea of abstracting the
DbContext under an
IDbContext interface. With this, the
Repository no longer depend on a specific context, but on this abstraction. This can help in separating the data access implementation in another assembly than the one in which we have generated the code for a specific context. This interface may be useful when you want that Dependency Injection or Service Locator to get directly involved on creating context instances and you need an interface for configuring the container. Otherwise, the same separation may be achieved with an abstract factory.
Another interesting thing is the
Repository.Get() does not return an
IEnumerable nor an
IQueryable. It returns this
RepositoryQuery<T> which is somehow in the middle. It limits the queries which may be defined, by giving
OrderBy functions and when you say
.Get() it composes the expressions and executes the query on the underlying repository.
It is hard to see the cases where the complexity added by this extra indirection pays off. It brings this common place where we can make sure that the queries written by upper layers may be adapted to work the same on any query provider, but unless we know that we need to easily replace EF with something else… might not worth doing it. Another advantage, I can think of, is that it enforces consistency in the client queries, but this needs to be balanced with the limitations it brings.
This article is part of a tutorial on www.asp.net about getting started with EF and MVC. It presents the step by step the process of evolving from a simple repository, which does nothing more than wrapping a
DbContext, towards a generic repository and then towards a unit of work that has more repository instances. It follows the same idea. An
UnitOfWork class which provides a generic repository through a get property. The
UnitOfWork takes care of creating and caching multiple repositories instances, one for each entity type. This may turn it into a complex factory.
Get() function of the repository returns an
IEnumerable. It receives through input parameters different parts of a query. There are parameters to give a filter expression, an order by expression or a string to specify the related entities. In my view this is an unfortunate choice. It does not give to the client a fluent API to specify the query. The client code has to tweak the parameters in different ways which increases the complexity on its side. There will also be cases when new
Get() overloads are needed to write in them the LINQ queries directly against the
DbContext. This may lead to inconsistency. I would rather return
IQueriable, or if not then have a
Get() without parameters which returns
IEnumeralble and explicitly ask for specific repository implementations which define other
Get() overloads for the specific queries.
I think the biggest take away from these blog posts is that there are many ways we can implement the repository and unit of work patterns. All implementations will achieve the same goal which is separating data access concern from the other concerns. The implementations differ because they are used in different context. All have pluses and minuses. What may work in one project, may not work in other, because the tradeoffs that are made differ from project to project. What is a critical plus for some teams in some contexts, may not be useful for others. What is not a big minus in some cases may prove to ruin projects in other contexts.
The more implementations of a pattern you see, the more design ideas you will get when you need to implement it for your current case. The more projects you’ve done where such patterns were used, the easier and faster it will be to implement it again for a new context or to evaluate possible implementations.