Repository Pattern¶
The Persistence Module includes Repository implementation with basic CRUD operations
Repositories implement the IRepository<TId, TEntity>
interface or
IReadOnlyRepository<TId, TEntity>
. An implementation is provided by the Suite.
For example the EFCore Module provides the EFCoreRepository<TId, TEntity>
which implements the interface using DbSets
.
There are two ways of using repositories, Generic and Custom.
Generic Repositories¶
Generic Repositories allows you to simply inject a repository interface for your entity whenever you need to perform basic operations, without the need to define its implementation.
When injecting an IRepository<TId, TEntity>
, or its read-only variant, they
will act as a factory; very similar to how the ILogger<T>
works.
Since applications may be composed of multiple DbContext
s, the generic
repositories relies on the IEntityTypeConfiguration<TDbContext, TEntity>
for
determining which DbContext
to use for an entity.
In practice, this means that when injecting a repository for an entity you don't
need to specify the DbContext
. Which is important, since the DbContext
is
something internal from the infrastructure layer implementation, and you'll most
likely want to inject repositories in your application layer, that is only aware
of your domain.
Let's see an example, first we define an
IEntityTypeConfiguration<TDbContext, TEntity>
for our entity:
C# | |
---|---|
For the sake of completeness, the DbContext
:
C# | |
---|---|
When we want to inject a repository for our entity, we simply need to inject
IRepository<int, Inspection>
:
That's the only thing you need to do, besides having a DbContext
properly
configured. You don't need to provide an implementation for
IRepository<int, Inspection>
; it will be proxied for your convenience.
Read Only Repositories¶
When you are performing read only operations it's best practice to inject a
IReadOnlyRepository<TId, TEntity>
, as we can see in the following example:
Adding Custom Behavior¶
There are basically two approaches for adding behavior to Repositories, extension methods and custom repository implementations. We recommend using the former whenever possible as it is cleaner, requires less code and doesn't require dealing with DI.
Extension Methods¶
To customize the behavior of a Repository you can write an
Extension Method
to encapsulate the complexity of the custom behavior nicely. For example inside
the extension method you can take advantage of the Specification library
supported by IRepository
.
On the example above we manage to add a FindByCodeAsync
method to our
IReadOnlyRepository<Guid, Issue>
without having to register a new type on the
DI.
These extension methods can be placed in the Domain layer in most cases, since Specifications are also in the Domain Layer. However, when using Include Specifications they will need to be added to the Persistence layer.
Custom Repositories¶
If after analyzing the option of using extension methods (which is the
recommended way for most cases) you still, for some reason, need to create a
custom IRepository
type, you can do it by following the instructions below.
This is also useful if you want to override/extend any of the Suite repository behaviors.
To get started you'll need to define an interface for your custom repository in your Domain layer.
Your new interface should not be generic and it should extend from
IRepository<TId, TEntity>
or its readonly variant.
For example:
C# | |
---|---|
Then we need to provide an implementation for your repository interface in your
infrastructure layer. You could implement all methods, but the recommended
approach is to extend EFCoreRepository<TDbContext, TId, TEntity>
and implement
only the custom methods that you've added to your interface.
Note that you'd also use your app's DbContext
as the TDbContext
when
extending EFCoreRepository
.
Things to note:
- We register the repository against the container using the interface. This will allow us to inject it by the interface later on, and we can leave the class internal to our infrastructure layer.
- Then we extend from
EFCoreRepository
so that we don't need to implementIRepository
methods. - We implement our custom repository interface and implement its methods.
Then, when we need to inject our repository, we do so by the interface:
Recommended approach¶
Use what you need. In a class that you simply need to read inject
IReadOnlyRepository
, if you need to modify data, inject IRepository
.
Remember, Least Privilege.
If later on, you realize you need a custom repository, create the interface and implementation. You can keep injecting the generic repository variant in your current code, you just need to register it with the container with both symbols. For example:
C# | |
---|---|