Repository Pattern – Generic Async Repositories in C#

Repository Pattern was first introduced in the Domain Driven Development back in 2004 and has since then gained a lot of popularity.
Today I’ll show you how you can implement a generic async version of it in C# (ASP.NET Core specifically).

The code of this blog post is available here.

DDD - Domain Driven Development

Since this blog post isn’t specifically about DDD, I will only quote what Steve Smith said in his amazing book Architecting Modern Web Apps with ASP.NET Core 2 and Azure about it:

Domain-Driven Design (DDD) is an agile approach to building software that emphasizes focusing on the business domain. It places a heavy emphasis on communication and interaction with business domain expert(s) who can relate to the developers how the real-world system works.

Repository Pattern

The Repository Pattern provides an abstraction (interface) over the data so that your application can work regardless of how the data is persisted, whether it will be saved to a database, a file or even a REDIS cache.

Repositories act almost like lists that you can add to, update, remove elements from and you would usually have one repository per entity (or value object).
This is highly useful because:

  1. The application core doesn’t know how the entities are saved, so you end up working with an interface that makes switching things up much easier in the infrastructure.
  2. It makes the application core testable. All you have to do now is mock the repository interface and test your business logic as much as possible.

About Entity Framework Core

Some people consider EF to implement a repository pattern and/or UoW, which it does.
But what if you one day to decided to switch to Dapper, a REDIS cache or even a NoSQL database? EF won’t help you in that case and your architecture should be flexible enough to allow these kind of changes without breaking anything.

Implementing a Generic Async Repository

Base Entity

In order to implement a generic repository, we will need a base entity class for all the entities/aggregation roots/value objects:

using System;

namespace ApplicationCore.Entities
{
    public class BaseEntity
    {

        public int Id { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime ModifiedDate { get;set;}

    }
}

Interface

The repository interface should include all the basic/useful functions that are needed from a repository without getting too specific.
Just because one entity needs a specific function doesn’t mean it belongs to the interface (will talk about this later).

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using ApplicationCore.Entities;

namespace ApplicationCore.Interfaces
{
    public interface IAsyncRepository<T> where T : BaseEntity
    {

        Task<T> GetById(int id);
        Task<T> FirstOrDefault(Expression<Func<T, bool>> predicate);

        Task Add(T entity);
        Task Update(T entity);
        Task Remove(T entity);

        Task<IEnumerable<T>> GetAll();
        Task<IEnumerable<T>> GetWhere(Expression<Func<T, bool>> predicate);

        Task<int> CountAll();
        Task<int> CountWhere(Expression<Func<T, bool>> predicate);

    }
}

EF Implementation

Now when we head to the implementation, regardless of which provider/framework you choose, it should be in the infrastructure and not in the application core.
A basic implementation of the IAsyncRepository in EF would be:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using ApplicationCore.Entities;
using ApplicationCore.Interfaces;
using Microsoft.EntityFrameworkCore;

namespace Infrastructure.Data
{
    public class EfRepository<T> : IAsyncRepository<T> where T : BaseEntity
    {

        #region Fields

        protected DataDbContext Context;

        #endregion

        public EfRepository(DataDbContext context)
        {
            Context = context;
        }

        #region Public Methods

        public Task<T> GetById(int id) => Context.Set<T>().FindAsync(id);

        public Task<T> FirstOrDefault(Expression<Func<T, bool>> predicate)
            => Context.Set<T>().FirstOrDefaultAsync(predicate);

        public async Task Add(T entity)
        {
            // await Context.AddAsync(entity);
            await Context.Set<T>().AddAsync(entity);
            await Context.SaveChangesAsync();
        }

        public Task Update(T entity)
        {
            // In case AsNoTracking is used
            Context.Entry(entity).State = EntityState.Modified;
            return Context.SaveChangesAsync();
        }

        public Task Remove(T entity)
        {
            Context.Set<T>().Remove(entity);
            return Context.SaveChangesAsync();
        }

        public async Task<IEnumerable<T>> GetAll()
        {
            return await Context.Set<T>().ToListAsync();
        }

        public async Task<IEnumerable<T>> GetWhere(Expression<Func<T, bool>> predicate)
        {
            return await Context.Set<T>().Where(predicate).ToListAsync();
        }

        public Task<int> CountAll() => Context.Set<T>().CountAsync();

        public Task<int> CountWhere(Expression<Func<T, bool>> predicate)
            => Context.Set<T>().CountAsync(predicate);

        #endregion

    }
}

Usage in ASP.NET Core

All you have to do to use this generic repository in ASP.NET Core is to add it in Startup.ConfigureServices as a scoped service:

services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));

With this, you will be able to inject a repository of a specific entity at any time.

About specific functions

In case you really need a specific function in your repository, for example GetByFirstName, you can’t really add it to the interface and implement because:

  1. It’s bad design since it’s specific to a certain entity.
  2. You won’t even be able to implement it since the generic repository works on the BaseEntity which doesn’t have a FirstName property (unless you add it, which is more catastrophic…).

What you should do instead is inherit the IAsyncRepository and EfRepository:

namespace ApplicationCore.Entities
{
    public class Worker : BaseEntity
    {

        public string FirstName { get;set;}

    }
}
using System.Threading.Tasks;
using ApplicationCore.Entities;

namespace ApplicationCore.Interfaces
{
    public interface IWorkerRepository : IAsyncRepository<Worker>
    {

        Task<Worker> GetByFirstName(string firstName);

    }
}
using System.Threading.Tasks;
using ApplicationCore.Entities;
using ApplicationCore.Interfaces;

namespace Infrastructure.Data
{
    public class WorkerRepository : EfRepository<Worker>, IWorkerRepository
    {

        public WorkerRepository(DataDbContext context) : base(context) { }

        public Task<Worker> GetByFirstName(string firstName)
            => FirstOrDefault(w => w.FirstName == firstName);

    }
}

And of course to use it you just add it as a scoped service too.
This is much elegant than just keep adding functions to the abstraction.

services.AddScoped<IWorkerRepository, WorkerRepository>();

At first it seems like the Repository Pattern is just a waste of time, even I thought that at first. But after I started working on a large project I understood the importance of not only this pattern but DDD in general.

Zanid Haytam Written by:

Zanid Haytam is an enthusiastic programmer that enjoys coding, reading code, hunting bugs and writing blog posts.

comments powered by Disqus