Why I am changing the Repository Pattern in .Net Core

Entity Framework Core already serves as unit of work. Therefore you don’t have to implement it yourself. This makes your code a lot simpler and easier to understand.

The Repository Pattern in .Net Core

For the demo, I am creating a simple 3-tier application consisting of controller, services, and repositories. The repositories will be injected into the services using the built-in dependency injection. You can find the code for the demo on Github.

In the data project, I have my models and repositories. I create a generic repository that takes a class and offers methods like get, add, or update.

Implementing the Repositories

 public class Repository<TEntity> : IRepository<TEntity> where TEntity : class, new()
    {
        private readonly RepositoryPatternDemoContext _repositoryPatternDemoContextContext;

        public Repository(RepositoryPatternDemoContext repositoryPatternDemoContextContext)
        {
            _repositoryPatternDemoContextContext = repositoryPatternDemoContextContext;
        }

        public IQueryable<TEntity> GetAll()
        {
            try
            {
                return _repositoryPatternDemoContextContext.Set<TEntity>();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't retrieve entities");
            }
        }

        public async Task<TEntity> AddAsync(TEntity entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
            }

            try
            {
                await _repositoryPatternDemoContextContext.AddAsync(entity);
                await _repositoryPatternDemoContextContext.SaveChangesAsync();

                return entity;
            }
            catch (Exception)
            {
                throw new Exception($"{nameof(entity)} could not be saved");
            }
        }

        public async Task<TEntity> UpdateAsync(TEntity entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException($"{nameof(AddAsync)} entity must not be null");
            }

            try
            {
                _repositoryPatternDemoContextContext.Update(entity);
                await _repositoryPatternDemoContextContext.SaveChangesAsync();

                return entity;
            }
            catch (Exception)
            {
                throw new Exception($"{nameof(entity)} could not be updated");
            }
        }
    }

This repository can be used for most entities. In case one of your models needs more functionality, you can create a concrete repository that inherits from Repository. I created a ProductRepository which offers a Product-specific method:

public class ProductRepository : Repository<Product>, IProductRepository
    {
        private readonly RepositoryPatternDemoContext _repositoryPatternDemoContextContext;

        public ProductRepository(RepositoryPatternDemoContext repositoryPatternDemoContextContext) : base(repositoryPatternDemoContextContext)
        {
            _repositoryPatternDemoContextContext = repositoryPatternDemoContextContext;
        }

        public Product MyProductSpecificMethod()
        {
            // Get some amazing data from the database and then return it;
            // return _repositoryPatternDemoContextContext.GetAmazingData();

            return new Product
            {
                Name = "ProductName",
                Description = "Special Description",
                Price = 1234567m
            };
        }
    }

The ProductRepository also offers all generic methods because its interface IProductRepository inherits from IRepository:

 public interface IProductRepository : IRepository<Product>
    {
        Product MyProductSpecificMethod();
    }

The last step is to register the generic repositories and the concrete repository in the Startup class.

services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
services.AddTransient<IProductRepository, ProductRepository>();

The first line registers the generic attributes. This means if you want to use it in the future with a new model, you don’t have to register anything else. The second line registers the concrete implementation of the ProductRepository.

Implementing Services which use the Repositories

I implement two services, the CustomerService and the ProductService. Each service gets injected a repository. The ProductServices uses the IProductRepository and the CustomerService uses the IRepository<Customer>. Inside the services, you can implement whatever business logic your application needs. I implemented only simple calls to the repository but you could have complex calculations and several repository calls in a single method.

public class CustomerService : ICustomerService
    {
        private readonly IRepository<Customer> _customerRepository;

        public CustomerService(IRepository<Customer> customerRepository)
        {
            _customerRepository = customerRepository;
        }

        public List<Customer> GetAllCustomer()
        {
            return _customerRepository.GetAll().ToList();
        }

        public async Task<Customer> GetCustomerById(int id)
        {
            return await _customerRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id);
        }

        public async Task<Customer> AddCustomer(Customer newCustomer)
        {
            return await _customerRepository.AddAsync(newCustomer);
        }
    }
 public class ProductService : IProductService
    {
        private readonly IProductRepository _productRepository;

        public ProductService(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }

        public Product GetMySpecialProduct()
        {
            return _productRepository.MyProductSpecificMethod();
        }

        public async Task<Product> GetProductById(int id)
        {
            return await _productRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id);
        }
    }

To get data, I am using GetAll for all my get methods. I can do this because GetAll returns an IQueryable object. This means that it contains only the call to the database but it wasn’t executed yet. In my GetProductById I use FirstOrDefault to get the first product with the id I want. This executes the database call and is fast because it only returns one (or no) product object.

return await _productRepository.GetAll().FirstOrDefaultAsync(x => x.Id == id);

Implementing the Controller to test the Application

To test the application, I implemented a really simple controller. The controllers offer for each service method a parameter-less get method and return whatever the service returned. Each controller gets the respective service injected.

 public class CustomerController
    {
        private readonly ICustomerService _customerService;

        public CustomerController(ICustomerService customerService)
        {
            _customerService = customerService;
        }

        public async Task<ActionResult<Customer>> CreateCustomer()
        {
            var customer = new Customer
            {
                Age = 30,
                FirstName = "Wolfgang",
                LastName = "Ofner"
            };

            return await _customerService.AddCustomer(customer);
        }

        public ActionResult<List<Customer>> GetAllCustomers()
        {
            return _customerService.GetAllCustomer();
        }

        public async Task<ActionResult<Customer>> GetCustomerById()
        {
            return await _customerService.GetCustomerById(1);
        }
    }
 public class ProductController
    {
        private readonly IProductService _productService;

        public ProductController(IProductService productService)
        {
            _productService = productService;
        }

        public async Task<ActionResult<Product>> GetProductById()
        {
            return await _productService.GetProductById(1);
        }

        public ActionResult<Product> GetSpecialProduct()
        {
            return _productService.GetMySpecialProduct();
        }
    }

When you call the create customer action, a customer object in JSON should be returned.

Test the creation of a customer Repository Pattern in .Net Core
Test the creation of a customer

Use the database

If you want to use the a database, you have to add your connection string in the appsettings.json file. My connection string looks like this:

 
"ConnectionString": "Server=localhost;Database=RepositoryPatternDemo;Integrated Security=False;Persist Security Info=False;User ID=sa;Password=<YourNewStrong@Passw0rd>"

I also added an SQL script to create the database, tables and test data. You can find the script here.

Conclusion

In today’s post, I gave my updated opinion on the repository pattern and simplified the solution compared to my post a couple of years ago. This solution uses entity framework core as unit of work and implements a generic repository that can be used for most of the operations. I also showed how to implement a specific repository, in case the generic repository can’t full fill your requirements. Implement your own unit of work object only if you need to control over your objects.

You can find the code for the demo on Github.