top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

Writer's pictureAhmed Tarek

Better Enhanced Repository Pattern Implementation in .NET C#

Updated: Apr 17

Learn how to implement a better enhanced Repository Design Pattern following Best Practices to satisfy extended requirements like throttling.


Implement a better enhanced Repository Pattern in DotNet (.NET) CSharp (C#) following best practices With Throttling/Paging, Dependency Injection (DI), Inversion of Control (IoC). Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by frank mckenna on Unsplash

The Story


Me and my team were going to start working on a big project. It was about a Web Application for a huge company. You can’t imagine how far I am tempted to reveal it’s name, but unfortunately, I can’t.


The client had some specific requirements and some of them were actually technical ones. I know this is not the normal thing to happen, but, this client already had his own team of technical people so that they can cope up with our team.


Long story short, let me skip the too long list of requirements and jump to the ones I am actually interested into for this article.


Implement a better enhanced Repository Pattern in DotNet (.NET) CSharp (C#) following best practices With Throttling/Paging, Dependency Injection (DI), Inversion of Control (IoC). Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Glenn Carstens-Peters on Unsplash

Requirements


As I said, our client had some specific requirements and some of them were technical ones. So, let me walk you through some of these interesting requirements and I am sure you would fall in love with them.


The application needed to have a backend to store all the data processed. And, Once I say Backend, you say… Repository.


The most interesting part of the requirements was related to the Repository to be implemented. Therefore, let me tell you about these ones.


 

1. Scheduled Automatic Health Check on Repositories


Requirement: The client wanted to be able to design, implement and run automatic health checks on all the repositories on scheduled tasks. The design should allow for blind health checks regardless of the repository type.


Design: For the design to allow this, there should be some high level abstraction of all the repositories in the system. This abstraction layer should expose as most as possible of the common APIs of all the repositories.


2. APIs Throttling Restrictions on Queries


Requirement: All queries should allow to apply some variable throttling restrictions to control the amount of data retrieved per call. The throttling criteria could be implemented as run-time logic instead of some hardcoded thresholds. Additionally, the caller should be informed about this and the way to get the rest of the data if possible.


Design: For the design to allow this, we should implement Paging to control the amount of data retrieved per call using the throttling logic. Also, some unified return object should be returned to the caller to inform him about the paging and how to get the next page,…


 


 

Implement a better enhanced Repository Pattern in DotNet (.NET) CSharp (C#) following best practices With Throttling/Paging, Dependency Injection (DI), Inversion of Control (IoC). Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Mikael Seegen on Unsplash

Disclaimer

  1. Some best practices would be ignored/dropped in order to drive the main focus to the other best practices targeted on this article.

  2. All the code could be found on this repository so that you can easily follow.

  3. Some details of the client and the nature of the project were intentionally not revealed as they are confidential.

  4. Some code implementation exists for demonstration purposes only and I don’t advise to use it or mimic it on production code. On such kind of occasions, a comment would be provided.

  5. Some understanding of other topics and concepts would be required to be able to fully understand the code and design in this article. In such cases, a note -and may be a link- would be provided.

  6. The paging implementation used in our example is specific for applying paging on memory lists. It is not intended to be used with production code which is using Entity Framework or any other frameworks.

  7. Please use the code on this article as a kickstarter or mind opener. Before going to production, you need to revise the design and adapt it to your own needs.


Implement a better enhanced Repository Pattern in DotNet (.NET) CSharp (C#) following best practices With Throttling/Paging, Dependency Injection (DI), Inversion of Control (IoC). Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Markus Spiske on Unsplash

Better Enhanced Implementation and Analysis


Entities

We would start with a very simple entity representing an employee. However, since our design in general would need some high level abstraction, we need to have an abstract Entity which would be inherited by all our system entities.



Entity



For our solution, we will keep it simple and don’t provide any common members for the abstract Entity class. However, for your own solution, you might need to do so.



Employee



What we can notice here:

  1. This is a simple Employee class which inherits the abstract Entity class.

  2. It has Id and Name.

  3. It is immutable and that’s why we have a constructor to copy all members -except the Id- from another Employee.

  4. We have an override of the ToString method to be used for demonstration purposes only. This is not a best practice to follow.



Paging

The paging concept was already explained before with code examples on the article Paging/Partitioning — Learn the Main Equations to Make it Easy.


If you wish to understand the paging code, I recommend that you first go and check that article.


I would include the paging code here for brevity and as a quick reference.



What we can notice here:

  1. This is the same code explained on the other article about Paging.

  2. The only added parts are about overriding the ToString method.

  3. This is done for demonstration purposes only. This is not a best practice to follow.


 


 

QueryResult

This leads us to the unified object which is returned from the Get repository calls which is in our case called QueryResult.



What we can notice here:

  1. We have the IQueryResult interface which represents the object to be returned from the Get repository calls.

  2. It has PagingDescriptor and ActualPageZeroIndex properties so that the caller would know the exact paging details of the data retrieved by his call.

  3. And, the Results property which is an IEnumerable of the abstract base Entity results.

  4. Note: The next part needs to be aware of two important concepts; Variance in .NET and Hiding Members in abstractions. If you need to read about these concepts, you can check the two articles Covariance and Contravariance in .NET C# and A Best Practice for Designing Interfaces in .NET C#.

  5. We defined the IQueryResult<out TEntity> generic interface so that we can have strong typed language support for our specific repositories.

  6. On this interface, we are hiding the IEnumerable<Entity> IQueryResult.Results property and replacing it with a new typed one.

  7. Then comes the QueryResult<TEntity> class which implements the IQueryResult<TEntity> interface.

  8. We have an override of the ToString method to be used for demonstration purposes only. This is not a best practice to follow.



AddOrUpdateDescriptor

We would support different methods on our repositories including AddOrUpdate methods. This would make the caller life easier whenever needed.


For that, we need to define a unified object structure to be returned from the AddOrUpdate method.



What we can notice here:

  1. We defined the IAddOrUpdateDescriptor interface.

  2. Also defined the AddOrUpdateDescriptor class which implements IAddOrUpdateDescriptor and it is immutable.

  3. Also defined the enum AddOrUpdate.



Query Repository

Now we move to our repository definitions. We would split our repository methods into two parts; Queries and Commands. I can hear you saying Command and Query Responsibility Segregation (CQRS).


Yes, our design would embrace the same concept of CQRS but it is not fully implemented here. So, please don’t judge this design as an incomplete CQRS as it was not intended to be in the first place.


Note: The next part needs to be aware of two important concepts; Variance in .NET and Hiding Members in abstractions. If you need to read about these concepts, you can check the two articles Covariance and Contravariance in .NET C# and A Best Practice for Designing Interfaces in .NET C#.



IQueryRepository



What we can notice here:

  1. This is our IQueryRepository interface which represents any non-generic Query Repository.

  2. We defined Entity Get(int id) method to get an entity by Id.

  3. We defined IQueryResult<Entity> GetAll() method to get all the entities inside the implementing repository. Please keep in mind that throttling restrictions should be applied and that’s why we are not just returning a list of entities, we are returning IQueryResult.

  4. We defined IQueryResult<Entity> Get(int pageSize, int pageIndex) method to get entities when divided into certain page size. Please keep in mind that throttling restrictions should be applied here as well. Therefore, if the throttling threshold is somehow set to 5, and the caller is requesting to get entities in pages of 8 entities per page, an adaptation would be applied and the caller would be aware of it by using the returned IQueryResult object.


 


 

IQueryRepository<TEntity>



Following the same main concept explained in the article A Best Practice for Designing Interfaces in .NET C#, we defined the IQueryRepository<TEntity> interface.


We also defined IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate) method to get entities after filtering them using the passed in predicate. Please keep in mind that throttling restrictions should be applied here as well.


Additionally, we defined IQueryResult<TEntity> GetByExpression(Expression<Func<TEntity, bool>> predicate, int pageSize, int pageIndex) method to apply paging after filtering the entities using the passed in predicate. Please keep in mind that throttling restrictions should be applied here as well.



QueryRepository<TEntity>



What we can notice here:

  1. This is an abstract class implementing IQueryRepository<TEntity>.

  2. The implementation of all the Get methods coming from the IQueryRepository<TEntity> interface would be delegated to the child classes inheriting from QueryRepository<TEntity>.

  3. For IQueryResult<Entity> IQueryRepository.GetAll(), it is defaulted to call the other IQueryResult<TEntity> GetAll() which would be implemented by child classes.

  4. And for Entity IQueryRepository.Get(int id), it is defaulted to call the other TEntity Get(int id) which would be implemented by child classes.

  5. And for IQueryResult<Entity> IQueryRepository.Get(int pageSize, int pageIndex), it is defaulted to call the other IQueryResult<TEntity> Get(int pageSize, int pageIndex) which would be implemented by child classes.



Command Repository

Here are the definitions related to any Command Repository.



ICommandRepository



What we can notice here:

  1. This is our ICommandRepository interface which represents any non-generic Command Repository.

  2. We defined all the required methods to interact with our repository. Methods like Add, Update, AddOrUpdate, and Delete.



ICommandRepository<in TEntity>



Following the same main concept explained in the article A Best Practice for Designing Interfaces in .NET C#, we defined the ICommandRepository<in TEntity> interface.


What worth to mention here is that starting from C#8.0, you can add this to an interface.

abstract int ICommandRepository.Add(Entity entity);

In our case, this means that although ICommandRepository<in TEntity> extends ICommandRepository, any class implementing ICommandRepository<in TEntity> interface would not expose int Add(Entity entity) method unless it is casted -implicitly or explicitly- into the non-generic ICommandRepository interface.



CommandRepository<TEntity>



What we can notice here:

  1. This is an abstract class implementing ICommandRepository<TEntity>.

  2. The implementation of all the methods coming from the ICommandRepository<TEntity> interface would be delegated to the child classes inheriting from CommandRepository<TEntity>.

  3. For int ICommandRepository.Add(Entity entity), it is defaulted to call the other int Add(TEntity entity) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.

  4. And for IEnumerable<int> ICommandRepository.Add(IEnumerable<Entity> entities), it is defaulted to call the other IEnumerable<int> Add(IEnumerable<TEntity> entities) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.

  5. And for void ICommandRepository.Update(Entity entity), it is defaulted to call the other void Update(TEntity entity) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.

  6. And for void ICommandRepository.Update(IEnumerable<Entity> entities), it is defaulted to call the other void Update(IEnumerable<TEntity> entities) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.

  7. And for IAddOrUpdateDescriptor ICommandRepository.AddOrUpdate(Entity entity), it is defaulted to call the other IAddOrUpdateDescriptor AddOrUpdate(TEntity entity) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.

  8. And for IEnumerable<IAddOrUpdateDescriptor> ICommandRepository.AddOrUpdate(IEnumerable<Entity> entities), it is defaulted to call the other IEnumerable<IAddOrUpdateDescriptor> AddOrUpdate(IEnumerable<TEntity> entities) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.

  9. And for bool ICommandRepository.Delete(Entity entity), it is defaulted to call the other bool Delete(TEntity entity) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.

  10. And for IDictionary<int, bool> ICommandRepository.Delete(IEnumerable<Entity> entities), it is defaulted to call the other IDictionary<int, bool> Delete(IEnumerable<TEntity> entities) which would be implemented by child classes, however, we added an extra check on the passed in type to make sure it matches the expected one.


 


 

Employee Query and Command Repositories

Now, this is the time to implement our Employee Query and Command repositories. This is where we start inheriting from our base abstract CommandRepository<TEntity> and QueryRepository<TEntity> classes.


However, first let’s clarify that for our example here we are not going to use a real database for storing our Employees data. What we are going to do instead, is to create a static list.



EmployeePersistence



It is as simple as a list of employees.



EmployeeQueryRepository



What we can notice here:

  1. The EmployeeQueryRepository class is inheriting from QueryRepository<Employee>.

  2. We don’t have to implement the methods coming from the non-generic IQueryRepository interface as they have been implemented in the abstract base QueryRepository<TEntity> class.

  3. All the Get methods are finally calling the centralized logic in the IQueryResult<Employee> Get(Func<Employee, bool> predicate, int? pageSize, int? pageIndex) method where the magic happens.

  4. On this method, the throttling is happening using the static MaxResultsCountPerPage and the Page extension method we implemented on the Paging section.



EmployeeCommandRepository



What we can notice here:

  1. The EmployeeCommandRepository class is inheriting from CommandRepository<Employee>.

  2. We don’t have to implement the methods coming from the non-generic ICommandRepository interface as they have been implemented in the abstract base CommandRepository<TEntity> class.

  3. The methods are simply implemented to use the static employees list.


 


 

Implement a better enhanced Repository Pattern in DotNet (.NET) CSharp (C#) following best practices With Throttling/Paging, Dependency Injection (DI), Inversion of Control (IoC). Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Bruno van der Kraan on Unsplash

Moment of Truth


Now, it is time to test our design. I created a Console Application for quick testing.


Testing would be divided into three parts:

  1. Demonstrating Basic Operations.

  2. Demonstrating Wrong Casting Checks.

  3. Demonstrating Dealing With Abstractions.


Therefore, we will end up with a Program class as follows:



We also defined a Student class which inherits from Entity and we are going to use it for testing at some point.


Now, we move to the implementation of the DemonstratingBasicOperation(), DemonstratingWrongCastingChecks(), and DemonstratingDealingWithAbstractions() one by one.



Demonstrating Basic Operations




We are calling

var result = m_EmployeeQueryRepository.GetAll();

followed by

Console.WriteLine(result);

and the result is:

ActualPageZeroIndex: 0
PagingDescriptor:
 - - - - - - - - 
ActualPageSize: 5
NumberOfPages: 2
PagesBoundries:
 - - - - - - - - 
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5
Results:
 - - - - - - - - 
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
Id: 4, Name: Sara

So, the Get All is working fine.



Then we are calling

result = m_EmployeeQueryRepository.Get(result.PagingDescriptor.ActualPageSize, result.ActualPageZeroIndex + 1);

followed by

Console.WriteLine(result);

and the result is:

ActualPageZeroIndex: 1
PagingDescriptor:
 - - - - - - - - 
ActualPageSize: 5
NumberOfPages: 2
PagesBoundries:
 - - - - - - - - 
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5
Results:
 - - - - - - - - 
Id: 5, Name: Ali

So, the Get With Paging is working fine.



Then we are calling

result = m_EmployeeQueryRepository.Get(6, 0);

followed by

Console.WriteLine(result);

and the result is:

ActualPageZeroIndex: 0
PagingDescriptor:
 - - - - - - - - 
ActualPageSize: 5
NumberOfPages: 2
PagesBoundries:
 - - - - - - - - 
FirstItemZeroIndex: 0, LastItemZeroIndex: 4
FirstItemZeroIndex: 5, LastItemZeroIndex: 5
Results:
 - - - - - - - - 
Id: 0, Name: Ahmed
Id: 1, Name: Tarek
Id: 2, Name: Patrick
Id: 3, Name: Mohamed
Id: 4, Name: Sara

Here we need to keep in mind the applied throttling restriction of 5 items. Analyzing this, we would get convinced that the Get With Paging is working fine.



Then we are calling

var tarek = m_EmployeeQueryRepository.Get(2);

followed by

Console.WriteLine(tarek);

and the result is:

Id: 1, Name: Tarek

So, the Get By Id is working fine.



Then we are calling

result = m_EmployeeQueryRepository.Get(emp => emp.Name.ToLower().Contains(“t”));

followed by

Console.WriteLine(result);

and the result is:

ActualPageZeroIndex: 0
PagingDescriptor:
 - - - - - - - - 
ActualPageSize: 2
NumberOfPages: 1
PagesBoundries:
 - - - - - - - - 
FirstItemZeroIndex: 0, LastItemZeroIndex: 1
Results:
 - - - - - - - - 
Id: 1, Name: Tarek
Id: 2, Name: Patrick

So, the Get With Predicate Filter is working fine.



Then we are calling

var erikId = m_EmployeeCommandRepository.Add(new Employee(0, “Erik”));

followed by

Console.WriteLine(erikId);

and the result is:

6

So, the Add is working fine.



Then we are calling

var added = m_EmployeeCommandRepository.Add(new []{ new Employee(0, “Hasan”), new Employee(0, “Mai”), new Employee(0, “John”) });

followed by

Console.WriteLine(String.Join(“\r\n”, added));

and the result is:

789

So, the Add Collection is working fine.



Then we are calling

m_EmployeeCommandRepository.Update(new Employee(1, “Tarek — Updated”));var tarekUpdated = m_EmployeeQueryRepository.Get(1);

followed by

Console.WriteLine(tarekUpdated);

and the result is:

Id: 1, Name: Tarek — Updated

So, the Update is working fine.



Then we are calling

m_EmployeeCommandRepository.AddOrUpdate(new Employee(1, “Tarek — Updated — Updated”));var tarekUpdatedUpdated = m_EmployeeQueryRepository.Get(1);

followed by

Console.WriteLine(tarekUpdatedUpdated);

and the result is:

Id: 1, Name: Tarek — Updated — Updated

So, the Add Or Update is working fine.



Then we are calling

var deletedTarek = m_EmployeeCommandRepository.Delete(1);

followed by

Console.WriteLine(deletedTarek);

and the result is:

True

And then calling

var checkTarek = m_EmployeeQueryRepository.Get(1); 

followed by

Console.WriteLine(checkTarek != null);

and the result is:

False

So, the Delete is working fine.


 


 

Demonstrating Wrong Casting Checks



We are defining the local variable queryRepository which is of type IQueryRepository by casting m_EmployeeQueryRepository.


var queryRepository = m_EmployeeQueryRepository as IQueryRepository;

And defining the local variable commandRepository which is of type ICommandRepository by casting m_EmployeeCommandRepository.


var commandRepository = m_EmployeeCommandRepository as ICommandRepository;

Then when trying to execute commandRepository.Add(new Student()); it fails with the exception System.ArgumentException: ‘The type “BetterRepository.Student” does not match the type “BetterRepository.Entities.Employee”’.


Then when trying to execute commandRepository.Add(new Student[] { new Student(), new Student() }); it fails with the exception System.ArgumentException: The type “BetterRepository.Student[]” does not match the type “System.Collections.Generic.IEnumerable`1[BetterRepository.Entities.Employee]”.


So, the Wrong Casting Check is working fine.



Demonstrating Dealing With Abstractions



First we are resetting the Employees list EmployeePersistence.Reset();.


Then, we are defining the local variable queryRepository which is of type IQueryRepository by casting m_EmployeeQueryRepository.

var queryRepository = m_EmployeeQueryRepository as IQueryRepository;

And defining the local variable commandRepository which is of type ICommandRepository by casting m_EmployeeCommandRepository.

var commandRepository = m_EmployeeCommandRepository as ICommandRepository;

Then we execute:

// Getting first two Employees when we actually don't know their type
// and we don't care about their type
var firstTwoItems = queryRepository.Get(2, 0);
Console.WriteLine(firstTwoItems);

And the result is:

Id: 0, Name: AhmedId: 1, Name: Tarek

And then we execute:

// Now we are deleting the first two items blindly when we don’t know their type// and we don’t care about their typecommandRepository.Delete(firstTwoItems.Results);

// Now we get the first two Employees again to check if it workedfirstTwoItems = queryRepository.Get(2, 0);Console.WriteLine(firstTwoItems);

And the result is:

Id: 2, Name: PatrickId: 3, Name: Mohamed

So, Dealing With Abstractions is working fine.


Implement a better enhanced Repository Pattern in DotNet (.NET) CSharp (C#) following best practices With Throttling/Paging, Dependency Injection (DI), Inversion of Control (IoC). Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Arthur Chauvineau on Unsplash

Final Words


Wow, a long trip. Now, let’s take a break, relax, and take a step back to see the big picture.


What we implemented here is not rocket science. The concepts are not that hard, it is only a matter of having steady hands while implementing it.


That’s it, hope you found reading this article as interesting as I found writing it.



Recent Posts

See All

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

© 2024 Development Simply Put  |  All rights reserved to Ahmed Tarek  |  Contact Us  |  Support Us  |  Privacy Policy  |  Cookie Policy  |  Terms & Conditions

  • LinkedIn
  • Twitter
  • Medium
  • GitHub
bottom of page
Mastodon Mastodon