Using the repository pattern is a common way to have better software architecture. One of the main benefits of the repository pattern is programming to an interface and not an implementation. This allows you to change the implementation of your code later on without breaking many of the changes. Your interface expects certain inputs and in return, you get consistent output. Let’s demonstrate this by creating an example. Also if you’re interested in more in-depth video tutorials, please check out our video section.

Setup

The first step will be creating a generic repository the methods that perform our basic CRUD functions. This includes Find, FindById, Create, Update and Delete methods.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ASPCore3.Models
{
    public interface IRepository
    {
        Task<List<T>> FindAll<T>() where T : class;
        Task<T> FindById<T>(long id) where T : class;
        Task CreateAsync<T>(T entity) where T : class;
        Task UpdateAsync<T>(T entity) where T : class;
        Task DeleteAsync<T>(T entity) where T : class;
    }
}

We then create our implementation. This takes a generic DBContext as a parameter. This allows for us to easily swap the type of DBContext we’ll use if our database happens to change. Each method will take a generic class that will be an entity model that maps to a table in the database.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ASPCore3.Models
{
    public class Repository<TDbContext> : IRepository where TDbContext : DbContext
    {
        protected TDbContext dbContext;

        public Repository(TDbContext context)
        {
            dbContext = context;
        }

        public async Task CreateAsync<T>(T entity) where T : class
        {
            this.dbContext.Set<T>().Add(entity);

            _ = await this.dbContext.SaveChangesAsync();
        }

        public async Task DeleteAsync<T>(T entity) where T : class
        {
            this.dbContext.Set<T>().Remove(entity);

            _ = await this.dbContext.SaveChangesAsync();
        }

        public async Task<List<T>> FindAll<T>() where T : class
        {
            return await this.dbContext.Set<T>().ToListAsync();

        }

        public async Task<T> FindById<T>(long id) where T : class
        {
            return await this.dbContext.Set<T>().FindAsync(id);

        }

        public async Task UpdateAsync<T>(T entity) where T : class
        {
            this.dbContext.Set<T>().Update(entity);

            _ = await this.dbContext.SaveChangesAsync();
        }
    }
}

Dependency Injection and Scoped Services

To be able to use the repository interface, we must configure dependency injection. In our Startup.cs file, we’ll add a scoped service that takes our interface and our implementation with a DbContext.

services.AddScoped<IRepository, Repository<ApplicationDbContext>>();

Next, we must go to our controller. I’ll use a Posts model and Post Controller in this example.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ASPCore3.Models
{
    public class Post
    {
        public long Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string Body { get; set; }
    }
}

Inside of the controller, we’ll pass in the interface. We don’t have to pass in our implementation call because our app knows which implementation to use based on the scoped service we set up.

private readonly IRepository _repository;

public PostsController(ApplicationDbContext context, IRepository repository)
{
       _repository = repository;
}

Now inside of our controller, if we want to get all of our posts, we can pass in our Entity model into the repository and we’ll get a result. The same can be done for the other CRUD functions as well.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ASPCore3.Data;
using ASPCore3.Models;

namespace ASPCore3.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PostsController : ControllerBase
    {
       
        private readonly IRepository _repository;

        public PostsController(ApplicationDbContext context, IRepository repository)
        {
            _repository = repository;
        }

        // GET: api/Posts
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Post>>> GetPost(int page = 0, int pageSize = 5)
        {
            var posts =  await _repository.FindAll<Post>();
            //var otherPost = await _context.Post.Skip(page * pageSize).Take(pageSize).ToListAsync();
            return posts;
        }

        // GET: api/Posts/5
        [HttpGet("{id}")]
        public async Task<ActionResult<Post>> GetPost(long id)
        {
            var post = await _repository.FindById<Post>(id);

            if (post == null)
            {
                return NotFound();
            }

            return post;
        }

        // PUT: api/Posts/5
        [HttpPut("{id}")]
        public async Task<IActionResult> PutPost(long id, Post post)
        {
            if (id != post.Id)
            {
                return BadRequest();
            }

            await _repository.UpdateAsync<Post>(post);

            return NoContent();
        }

        // POST: api/Posts
        [HttpPost]
        public async Task<ActionResult<Post>> PostPost(Post post)
        {
            await _repository.CreateAsync<Post>(post);
            return CreatedAtAction("GetPost", new { id = post.Id }, post);
        }

        // DELETE: api/Posts/5
        [HttpDelete("{id}")]
        public async Task<ActionResult<Post>> DeletePost(long id)
        {
            var post = await _repository.FindById<Post>(id);

            if (post == null)
            {
                return NotFound();
            }

            await _repository.DeleteAsync<Post>(post);

            return post;
        }

        
    }
}

Conclusion

Setting up the repository pattern is a simple technique that you can use to make your code both maintainable and reusable. It allows for you to focus on changing the code in every instance your code is called by registering the implementation to an interface that can be used instead. If you’re interested in learning more, please check out our video courses in the link here.

Codebrains Newsletter

Get weekly dev news and tutorials.

Powered by ConvertKit