This is one of the most used and recommended patterns to apply in an application that works with any type of database. It provides a simple way to create a standard for your CRUD operations. If you like generics, you will love this pattern.
Why is it so good?
Because centralizes all the basic operations regarding an entity in one main class. Without this approach, we would have multiple classes with the same logic but for a different entity.
The following code block shows the main interface:
public interface IRepository<TEntity>
{
TEntity Find(params object[] keyValues);
IEnumerable<TEntity> FindAll();
void Insert(TEntity entity);
void Update(TEntity entity);
void Delete(TEntity entity);
}
As we can see, we have all the 4 basic operations for an entity. The interface is for a generic entity in order to allow us to use it for any existing entity.
How can I access the database?
You can use any ORM of your preference, in this case, I will use the Entity Framework Core. This library gives us the DbContext class which basically represents a database itself. It also gives us the DbSet<TEntity> class to represent all entities in the context of a given type.
Keeping these in mind let’s see some code:
public TEntity Find(params object[] keyValues)
{
return _dbSet.Find(keyValues);
}public IEnumerable<TEntity> FindAll()
{
return _dbContext.Set<TEntity>();
}public void Insert(TEntity entity)
{
_dbSet.Add(entity);
}public void Update(TEntity entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
}public void Delete(TEntity entity)
{
_dbSet.Remove(entity);
}
As you can imagine this implementation depends on the ORM that you choose. Using the generics approach makes sense when looking at the code. The behavior for an Insert operation is the same for all types of entities in the database.
The constructor of this class is quite simple. We just need to receive a database context and create a DbSet instance for the type of the repository.
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly DbContext _dbContext;
private readonly DbSet<TEntity> _dbSet; public Repository(DbContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<TEntity>();
}
}
How can I create my own queries to read some entities?
What a great question! The Find method is not enough because we will probably need more specific queries for some entities. You can create extensions classes for each repository type that you want to add queries. To accomplish this we need to add a new method to the IRepository interface.
public interface IRepository<TEntity>
{
TEntity Find(params object[] keyValues);
IEnumerable<TEntity> FindAll();
void Insert(TEntity entity);
void Update(TEntity entity);
void Delete(TEntity entity); IQueryable<TEntity> Queryable();
}
The Queryable method will give us the current DbSet instance for the specific repository type. Let’s see the implementation:
public IQueryable<TEntity> Queryable()
{
return _dbSet;
}
The next code block shows an example of an implementation of an extension class for the entity Person with new queries.
public static class PersonRepository
{
public static Person FindByIdAndName(
this IRepository<Person> repository,
int id,
string name)
{
return repository.
Queryable().
Where(p => p.Id == id && p.Name == name).
SingleOrDefault();
}
}
Unit Of Work Pattern Importance
We already saw the advantages of using the Repository Pattern. However, this pattern increases its capacity using also the Unit Of Work. You can see the details of this pattern in my other post here.
The Unit Of Work class is the one that has the DbContext instance. So the Repository implementation doesn’t need to receive it, just need to have a dependency from Unit Of Work. Let’s see the code to clarify what I’m saying:
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly IUnitOfWork _unitOfWork;
private readonly DbContext _dbContext;
private readonly DbSet<TEntity> _dbSet; public Repository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_dbContext = unitOfWork.DbContext;
_dbSet = _dbContext.Set<TEntity>();
}
}
As can we see above, in bold, the changes that were made in the Repository class were pretty small. The main difference is in the fact that we access the database context through the Unit Of Work.
Is it possible to access another repository in a repository extension class?
With the interfaces presented until now, the answer is no. However, if we need this type of feature is simple if we already use the Unit Of Work pattern, as shown previously.
public interface IRepository<TEntity>
{
TEntity Find(params object[] keyValues);
IEnumerable<TEntity> FindAll();
void Insert(TEntity entity);
void Update(TEntity entity);
void Delete(TEntity entity); IQueryable<TEntity> Queryable();
IRepository<TEntity> GetRepository();
}
To a better understanding of the implementation of this method, I really recommend you read my post about Unit Of Work.
public IRepository<TEntity> GetRepository()
{
return _unitOfWork.Repository<TEntity>();
}
So if we need to get some information about another repository in the PersonRepository presented earlier, we just need to do the following:
public static class PersonRepository
{
public static bool IsCompanyActive(
this IRepository<Person> repository,
Person person)
{
return repository.
GetRepository<Company>().
Queryable().
Where(c => c.Id == person.CompanyId).
SingleOrDefault().IsActive;
}
}
How do I register the repositories?
You need to add your repositories to the IServiceColletion as you usually do for any Dependency Injection.
services.AddScoped<IRepository<Person>, Repository<Person>>();
services.AddScoped<IRepository<Company>, Repository<Company>>();
Suggestion: If you have multiple repositories you could create an extension class for IServiceCollection in order to keep the responsibility of register repositories in the repository layer.
private static void AddRepositories(this IServiceCollection services){
services.AddScoped<IRepository<Person>, Repository<Person>>();
services.AddScoped<IRepository<Company>, Repository<Company>>();
}
What do I need to do when I have new entity classes?
After you create your entities you just need to register the new type of repository in your dependencies injection registry. Let’s say you have a new User entity.
private static void AddRepositories(this IServiceCollection services){
services.AddScoped<IRepository<Person>, Repository<Person>>();
services.AddScoped<IRepository<Company>, Repository<Company>>();
services.AddScoped<IRepository<User>, Repository<User>>();
}
Since you have this new dependency defined, you are able to use the repository to access the User entity to get or create data from the database. As you can see, your job is to focus on the creation of the new entity. The implementation of Repository Pattern provides you the CRUD operations almost for free.
Conclusion
The Repository Pattern allows us to keep our code more maintainable. If you have any problem with an insert or update operation you know that there is just one point to check the unexpected behavior. The possibility to create extensions classes for each repository in order to add more specific queries gives us the extensibility that we want.
Last but not least, don’t forget the multiples advantages which to implement the Unit Of Work Pattern provide us. It is the key to improve the strength of the Repository Pattern.
Source: Medium
The Tech Platform
Comments