Enterprise Design Pattern - Repository
Design Pattern
10 Articles
In this article, let's learn about Repository Design Pattern in .NET.
Table of Contents
- Introduction
- What is Repository Pattern?
- Implementing a Repository Pattern
- Generic Repository
- Non Generic Repository
- Combining Generic and Non Generic Repository
- FAQs
- Summary
Introduction
The Repository pattern is a commonly used enterprise design pattern, especially in enterprise applications. It helps to abstract and encapsulate data access logic, promoting a clean separation of concerns between business logic and data access logic.
What is Repository Pattern?
The Repository pattern provides a way to manage data access with minimal code duplication and improved testability. It abstracts the data layer, making the business logic unaware of the underlying data source. This allows developers to switch persistence technologies, if needed, without impacting the business logic. Repository Pattern in other words allows developer to have a specific persistence for each repository based on need.
Instead of directly interacting with the database using tools like ADO.NET or Entity Framework Core, the business logic communicates with repositories. These repositories provide a set of methods to perform CRUD (Create, Read, Update, Delete) operations on data entities.
Implementing Repository Pattern
A typical repository starts with a contract, often defined as an interface. This interface defines common data operations that can be reused across different entities, reducing redundancy in your codebase. For example:
Code Sample - Generic Repository
Generic Repository
A generic repository allows you to define common CRUD operations once and reuse them across multiple entities, reducing code duplication. In this example, lets take EF Core as the ORM and define a generic repository that implements the IRepository interface.
Code Sample - Generic Repository Implementation
Advantages
- Reusability: Common data operations are defined once and reused across multiple entities.
- Consistency: All entities have a consistent API for CRUD operations.
Disadvantages
- Lack of Specificity: May not provide methods tailored to specific entities, requiring additional custom methods.
- Over-generalization: Can become too abstract, making it difficult to implement entity-specific logic.
Non Generic Repository
Non-generic repositories are tailored to specific entities. They define custom methods that are unique to the entity they represent. But also have the same methods defined in the generic repository. For example:
Code Sample - Non Generic Repository
Advantages
- Specificity: Tailored to the needs of specific entities, making them more flexible for complex queries and operations.
- Clear Intent: The repository's purpose is clear, as it deals with a specific entity.
Disadvantages
- Potential Code Duplication: Common CRUD operations may be repeated across different repositories.
- Maintenance Overhead: Managing multiple repositories can become cumbersome in large projects.
Combining Generic and Non Generic Repository
In practice, you can mix both approaches. Use a generic repository for common operations and a non-generic repository for entity-specific methods. All we need to do is to create a non-generic repository that inherits from the generic repository and implements the entity-specific methods. This way, we can reuse the common CRUD operations defined in the generic repository and add custom methods as needed reducing massive code duplication.
Code Sample - Combining Generic and Non Generic Repository
Code Sample - Combining Generic and Non Generic Repository Implementation
FAQs
Entity Framework Core and the Repository Pattern
Entity Framework Core (EF Core) can be used with the Repository pattern to simplify data access. While EF Core provides a built-in repository pattern, you can choose whether to use it directly or implement your own.
Should You Use EF Core's Built-In Repository?
Entity Framework Core provides a repository implementation via the DbSet. But it is considered a bit leaky, so it's often encapsulated in another repository just as we did in the demos.
Multiple SaveChangesAsync()?
The problem with multiple SaveChangesAsync() calls in each respository is that they can lead to inconsistent data states. To avoid this, consider using a Unit of Work pattern to manage transactions across repositories which we will cover in next article.
Summary
The Repository pattern, whether generic, non-generic, or a combination, is a powerful tool for managing data access in a clean, maintainable way. It enhances testability, flexibility, and maintainability, allowing your application to adapt as requirements change. But, we've currently got those SaveChanges called in our repositories, does such a method really belong there? And what if we need to work with transactions across repositories? That is where the Unit of Work pattern comes in. Let's have a look at that one next.