Entity Framework Core Generic Repository

Introduction

Entity Framework Core Generic Repository – Behold! The topic that some people will frown upon. They don’t even wanna talk about it. However, others love it, they feel all excited on the mention of generic repository pattern.

As with everything, the generic repository pattern has its pros and cons. You are the one to decide if it’s a good fit for your project. You can always use it only for parts of your application, you don’t have to go all in with generic repository pattern!

The advantage of having generic CRUD repository is that you can inherit from it, pass it entity type and you have CRUD repository for any entity type with a minimal amount of code.

Note: We will not build something bullet-proof that will cover all of your needs in future. Rather, we will try to build a base for a generic repository that you will be able to use to create CRUD operations easily and later extend per your needs.  So, this will only cover Generic Repository that does CRUD. For anything additional, you could inherit from GenericRepository class and extend it. This is just a proof of concept. In larger real-world applications you don’t want your Web layer to be aware of your Database layer. Hence, you will not be injecting repositories in your controllers.

Again, you could only use it for parts of your application. You don’t have to use it as a de-facto solution for all of your database needs.

Sample code – here.

 

Set up the DbContext

I will create a new MVC template with ASP.NET Core 2, without auth.

Let’s set up DbContext and some sample DB Entity class. Here is the Category Database Entity:

public class Category
{
    public int Id { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }
}

And here is the DbContext:

public class CodingBlastDbContext : DbContext
{
    public CodingBlastDbContext(DbContextOptions<CodingBlastDbContext> options)
        : base(options)
    {
    }

    public DbSet<Category> Categories { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}

You can see we have Categories DbSet which represents a matching table in the database.

We are creating a constructor that accepts DbContextOptions as a parameter. This will enable us to pass in options from ConfigureServices in Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CodingBlastDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddMvc();
}

We will add a connection string to appsettings.json so I can actually interact with the database:

"ConnectionStrings": {
  "DefaultConnection": "Server=.;Database=GenericRepository;Trusted_Connection=True;MultipleActiveResultSets=true"
},

Before being able to use Entity Framework’s command line tool I need to add it to my project. I will manually edit the .csproj file and this:

<ItemGroup>
  <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>

For changes to take effect I have to restore packages (you don’t have to do this if you are using Visual Studio to make these changes):

dotnet restore

Now I am ready to add initial migration and apply it to the database:

dotnet ef migrations add Initial
dotnet ef database update

 

Building generic repository

Just like with Entity Framework 6, in EF Core we use DbContext to query a database and group together changes that will be written back to the store as a unit.

The great thing about DbContext class is that it’s generic and therefore supports generics on methods that we will use to interact with the database.

IGenericRepository interface

The base interface that we will use looks like this:

public interface IGenericRepository<TEntity>
 where TEntity : class, IEntity
{
    IQueryable<TEntity> GetAll();

    Task<TEntity> GetById(int id);

    Task Create(TEntity entity);

    Task Update(int id, TEntity entity);

    Task Delete(int id);
}

The first thing you will notice is generic TEntity type, that will be the type of our entity in Database (Category, User, Role, etc.).

We also set a constraint that TEntity needs to be class. We can also use some interface to mark an entity and use that interface in each of entity classes. But we will talk about that later.

GetAll returns IQueryable because we don’t want to return full list. However, we want to return something that caller will be able to use to further process the query. Maybe filter it by something, do paging, etc. That’s no interest of us for now.

Other method signatures are probably just like you would expect them. However, you might notice Task in front of the methods. That is because these methods will be async because we will be making use of Entity Framework’s async support.

Let’s start implementing generic repository:

public class GenericRepository<TEntity> : IGenericRepository<TEntity>
    where TEntity : class, IEntity
{
    private readonly CodingBlastDbContext _dbContext;

    public GenericRepository(CodingBlastDbContext dbContext)
    {
        _dbContext = dbContext;
    }
}

We will have ASP.NET Core inject DbContext for us that’s why we are passing it in as a constructor parameter.

Now you will see that compiler is complaining because we are not implementing IGenericRepository. Time to do that!

The GetAll method is pretty straightforward:

public IQueryable<TEntity> GetAll()
{
    return _dbContext.Set<TEntity>().AsNoTracking();
}

We simply return the belonging DbSet for the Entity. And since DbSet implements IQueryable we can use it to process the queryable further later on. We are using AsNoTracking extension to make things faster and prevent any updates to this specific IQueryable collection. In your project you probably don’t want this in your web layer, yo woud use a layer between to process your entities.

And then we have GetById method.

public async Task<TEntity> GetById(int id)
{
    return await _dbContext.Set<TEntity>()
                .AsNoTracking()
                .FirstOrDefaultAsync(e => e.Id == id);
}

It simply gets the data for given id and finds the entity with this unique id.

And then there are methods that actually do change in database:

public async Task Create(TEntity entity)
{
    await _dbContext.Set<TEntity>().AddAsync(entity);
    await _dbContext.SaveChangesAsync();
}

public async Task Update(int id, TEntity entity)
{
    _dbContext.Set<TEntity>().Update(entity);
    await _dbContext.SaveChangesAsync();
}

public async Task Delete(int id)
{
    var entity = await GetById(id);
    _dbContext.Set<TEntity>().Remove(entity);
    await _dbContext.SaveChangesAsync();
}

For each method, we use the appropriate method to do the operation and afterwards we save the changes, making use of EF’s SaveChangesAsync() method.

In Delete method, we use our existing GetById method to fetch the existing entity from DB and then we pass in that entity to Remove method od DbSet.

 

Injecting repository in controller

Let’s create an interface for Category repository:

public interface ICategoryRepository: IGenericRepository<Category>
{
  Task<Category> GetCoolestCategory();
}

This inherits from IGenericRepository interface, meaning that we will have to implement all these methods.

However, if we create a CategoryRepository that inherits from GenericRepository, we will cover all those methods from IGenericRepository. Also, we have to implement also ICategoryRepository.

This is how it would look like:

public class CategoryRepository : GenericRepository<Category>, ICategoryRepository
{
    public CategoryRepository(CodingBlastDbContext dbContext)
        : base(dbContext)
    {
            
    }
}

By inheriting from GenericRepository we just have CRUD done for CategoryRepository. Which is great! You write a small amount of code and you get CRUD done for any entity type.

However, we also need to implement ICategory repository, so our CategoryRepository class will look like this:

public class CategoryRepository : GenericRepository<Category>, ICategoryRepository
{
    public CategoryRepository(CodingBlastDbContext dbContext)
        : base(dbContext)
    {
            
    }

    public async Task<Category> GetCoolestCategory()
    {
        return await GetAll()
            .OrderByDescending(c => c.Name)
            .FirstOrDefaultAsync();
    }
}

 

Fetching the actual data

For us to be able to use CategoryRepository inside of our code we have to add it to DI container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CodingBlastDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    
    services.AddMvc();
                
    services.AddScoped<ICategoryRepository, CategoryRepository>();
}

Now we can inject it in the controller:

using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using GenericRepository.Data;
using GenericRepository.Models;

namespace GenericRepository.Controllers
{
    public class HomeController : Controller
    {
        private readonly ICategoryRepository _repository;

        public HomeController(ICategoryRepository repository)
        {
            _repository = repository;
        }
        
        public async Task<IActionResult> Index()
        {
            var category = new Category
            {
                Description = "first category - very cool description",
                Name = "First category"
            };
            await _repository.Create(category);

            var coolestCategory = await _repository.GetCoolestCategory();
            
            return View();
        }

        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

 

Ibrahim Šuta

Software Consultant interested and specialising in ASP.NET Core, C#, JavaScript, Angular, React.js.