In past few articles, we have seen how to design schema using EF core. We also created data access layer, business layer and created a .NET core web API with Swagger for blog application.
In this implementation, we used repository pattern. A common IBaseRepository interface was used and a common abstract generic implementation BaseRepository<T> was created to avoid writing same code again and again.
Is Repository Pattern Bad ?
Repository pattern is not really a mandate for using EF Core or any other framework. It is just a practice that is followed by many developers to setup project initially. Generally every body agrees, that it is better to have base interface repository and then implement that interface in concrete repository. The discussion about whether repository is an anti pattern starts at the BaseRepository class.
A BaseRepository class can be helpful to avoid writing same code again and again for different entities. Let’s talk about Get All method from repository. Every entity might have different columns and every entity might want to add certain conditions so that limited data is retrieved from database.
But if a Generic Repository ( in our case BaseRepository) provides a GetAll implementation, we may mistakenly write code which read all records from database, bring them to application and then filter the resultset now in .NET application. This might impact performance of the system as everytime all the records are being read.
Let’s consider an application which has User entity. Let’s say that application needs to show different types of users on different screens:
- Screen 1 to show all admin users (role == admin)
- Screen 2 to show all users deleted in last 7 days (assuming delete operation was soft delete)
- Screen 3 to show all users who have not used application in last 1 month
Now, all those screen need to get users, but the condition clause of query would differ for each screen. So, the GetAll method from BaseRepository cannot be used. In repository pattern, the generic BaseRepository class cannot have entity specific methods. This would mean many entities might have to create their own version of reading the data, just because the conditions required to fetch the data differ.
So is it really an anti-pattern ?
But, is this sufficient to conclude that repository is an anti-pattern ? Certainly not. Repository pattern is still helpful and can save considerable time of typing similar lines of code in repository layer. So, repository pattern is not really an anti-pattern.
If any pattern is not applied properly, it may cause chaos. It may result in bad code.
So, if repository pattern is not anti-pattern, then:
- What to do when
GetAllmethods are required to return data based on different conditions ?
- Would a generic base repository be able to apply those conditions ?
- Would generic repository be able to specify navigation properties to load related data of an entity ?
- Would generic repository able to sort the records in specific order ?
Here comes the Specification Pattern !
Specification pattern can help to answer the questions mentioned above. This pattern may look bit complex initially. The idea here is:
- Create a base IBaseSpecifications interface
- Create an BaseSpecifications class, which implements the interface. This class provides some basic implementation to specify filter conditions & sort conditions in a generic way.
- SpecificationEvaluator to apply all expressions to IQueryable
- Then if required, create concrete specification classes from BaseSpecifications. Otherwise, BaseSpecifications can be used.
So every time new conditions are required to be set,
- An concrete
Specification classcan be instantiated and can be populated with all required conditions.
- This class can be passed as parameter to base repository.
- And base repository would apply those conditions.
All of this is generic implementation and the entity specific classes would be minimal, only populating certain collections defined in abstract BaseSpecification.
Let’s have a look at how specification pattern can be implemented. We are going to use the blog demo app that we have created in previous articles.
Let’s define this interface in such a way that it provides properties to hold below specifications:
- Filter specifications
- Sort specifications (e.g.
- Group By clause
- Related data specifications (i.e. Include)
To hold this information, data type for those properties would be Expression<TDelegate>, where TDelegate would be a Func delegate. Below code snippet shows the interface.
In the blog app, this interface can be added in data access layer as it would hide specification pattern and repository pattern details from higher layers.
This interface defines only 4 properties, but you can add some additional properties too (e.g. to support pagination, limiting records by specifying number of rows, etc.)
This class is self explanatory. It just has properties that came from interface and then some protected methods to set those properties.
This class is important one. This class takes IQueryable and IBaseSpecifications<T> as parameters. The parameters specified in IBaseSpecifications<T> are applied to IQueryable. If no specifications were passed, the query is not modified.
We have two queries in BaseRepository –
GetByIdAsync. For this demo, we will modify single method –
GetByIdAsync. This method would accept an optional parameter IBaseSpecifications<TEntity>, which is defaulted to NULL. So, if specifications are specified, then query is modified and additional expressions are added to it.
Now, in business layer, we can create a new specification for reading post data – PostRelatedDataSpecifications. This class would add includes for all reference properties so that all related data can be loaded eagerly.
Then, PostsBusiness can pass instance of this specifications class.
After this, when you try to call
GetById endpoint on PostsController, it should load related data too. In our solution, we need specification only for Post entity.
In real world applications, there might be more relationships defined in database and this specification pattern would be very helpful to quickly setup repositories. It would also help avoiding chaos of methods in a given repository, which just differ from one another only one or more expressions.
I hope you find this information helpful. Let me know your thoughts.
This Post Has 3 Comments
Pingback: The Code Blogger - .NET – Composite Specifications using EF Core
SpecificationEvaluator return type and GroupBy() return type is different hence it’ll fail at run time. You need to handle it differently. Rest is ok.
Repository pattern is swapped with TableGateway pattern. It is because DbContext in EntityFramework implements Unit of Work and Repository pattern. Generic repository is more TableGateway because it works with POCO entity. But in rich domain model there are entities with behavior or owned entities. In domain model entity might not have business identity or identity is given with root entity identity together (aggregates in domain model). Repository is implemented for root entities only. It means one repository can handle with more tables to get whole aggregate. This is main difference between repository and table gateway patterns. How to choose which pattern I need to use? It depends on business pattern which you use. If you use domain model it is very hard to use table gateway pattern and repository will be better choice. When you use transaction script or table module, repository pattern will be degraded to table gateway because you don’t need to work with whole aggregate but work with one table make more sense.