The repository pattern aims to create an abstraction layer on top of the data access layer, by doing so it creates a separation of concerns between the business logic and data storage mechanism. I'll try to explain in simple terms how to use these patterns in a small example and what are the benefits that come with them.

The hypothetical example for this article will be a sample application, with three protagonists: Owner, Car, and Service.

Repository Pattern

To implement the repository pattern, you need to have a separate class for each entity that you want to manage. This class will be responsible for all data-related operations to the entity. So, in this case, we will have 3 repositories, one for each domain type. Also taking into considerations that we are trying to create an acceptable generic example, we must write for testability and abstraction, so each of these classes will have its interface.

Creating repositories

Starting with the Owner entity, here's the interface with all the supported operations:

public interface IOwnerRepository
{
    IEnumerable<Owner> GetAll();
    Owner Get(Guid Id);
    void Insert(Owner model);
    void Update(Owner model);
    void Delete(Owner model);
}

And the implementation of the interface with dbcontext defined in a class variable, so this class expects the calling code to pass in an instance of that dbcontext:

public class OwnerRepository : IOwnerRepository, IDisposable
{
    private readonly ApplicationDbContext context;
    public OwnerRepository(ApplicationDbContext applicationDbContext)
    {
        context = applicationDbContext;
    }

    public Owner Get(Guid Id)
    {
        return context.Owners.Find(Id);
    }

    public IEnumerable<Owner> GetAll()
    {
        return context.Owners.ToList();
    }

    public void Insert(Owner model)
    {
        context.Owners.Add(model);
    }

    public void Update(Owner model)
    {
        context.Entry(model).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
    }

    public void Delete(Owner model)
    {
        context.Owners.Remove(model);
    }
}

The same logic and implementation are valid for Car and Service entities, however, if you notice the interface implementations code, the SaveChanges() method call is missing from create, update and delete operations. The reason is that we want a transactional behavior when issuing data modification commands. Say, for instance, every time a new record is inserted into Service table, the related Car.LastService property needs to be updated too, and we need that to succeed or rollback as an atomic operation. Such behavior is possible with the Unit of Work pattern.

Unit Of Work

This layer will be responsible for creating and managing repositories and DbContext objects:

public interface IUnitOfWork
{
    ICarRepository CarRepository { get; }
    IOwnerRepository OwnerRepository { get; }
    IServiceRepository ServiceRepository { get; }

    void Commit();
}

And the Unit of work implementation will make sure there's only one instance of a DbContext at a time, and all the behavior of the repositories will be accessed through this layer instance.

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private ICarRepository _carRepository;
    private IOwnerRepository _ownerRepository;
    private IServiceRepository _serviceRepository;
    private readonly ApplicationDbContext _context;

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
    }

    public ICarRepository CarRepository
    {
        get
        {
            if (_carRepository == null)
                _carRepository = new CarRepository(_context);

            return _carRepository;
        }
    }

    public IOwnerRepository OwnerRepository
    {
        get
        {
            if (_ownerRepository == null)
                _ownerRepository = new OwnerRepository(_context);

            return _ownerRepository;
        }
    }

    public IServiceRepository ServiceRepository
    {
        get
        {
            if (_serviceRepository == null)
                _serviceRepository = new ServiceRepository(_context);

            return _serviceRepository;
        }
    }

    public void Commit()
    {
        _context.SaveChanges();
    }
}

Repository Client

I've set up a webapi application to act as a client for this service so in Startup.cs inject the unit of work

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

    services.AddScoped<IUnitOfWork, UnitOfWork>();
}

And then use it from the controller:

[Route("api/[controller]")]
[ApiController]
public class ServiceController : ControllerBase
{
    private readonly IUnitOfWork _unitOfWork;

    public ServiceController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var dbItems = _unitOfWork.ServiceRepository.GetAll();
        return Ok(dbItems);
    }

    [HttpPost]
    public IActionResult Post([FromBody] Service model)
    {
        var dbCar = _unitOfWork.CarRepository.Get(model.CarId);
        dbCar.LastService = DateTime.Now;

        _unitOfWork.CarRepository.Update(dbCar);
        _unitOfWork.ServiceRepository.Insert(model);

        _unitOfWork.Commit();

        return Ok();
    }
}

Generic Repository

While the Repository pattern has it's own use, a lot of crud alike applications won't benefit much of it. So instead of creating one repository for each entity, there is just one generic repository for all entities, here's the interface:

public interface IRepository<T> where T : EntityBase
{
    T Get(Guid id);

    IEnumerable<T> Get(
        Expression<Func<T, bool>> filter = null,
        Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
        string[] include = null
        );

    void Insert(T entity);

    void Update(T entity);

    void Delete(T entity);
}

And the generic implementation from which a client can issue operations such as Find, Search with a custom query, Sort, Insert, update and delete:

public class Repository<T> : IRepository<T> where T : EntityBase
{
    protected DbContext _context;
    protected DbSet<T> dbSet;

    public Repository(DbContext context)
    {
        _context = context;
        dbSet = _context.Set<T>();
    }

    public T Get(Guid id)
    {
        return dbSet.Find(id);
    }

    public virtual IEnumerable<T> Get(
        Expression<Func<T, bool>> filter = null,
        Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
        string[] include = null)
    {
        IQueryable<T> query = dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        if (include != null)
        {
            foreach (var item in include)
            {
                query = query.Include(item);
            }
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

    public void Insert(T entity)
    {
        dbSet.Add(entity);
    }

    public void Update(T entity)
    {
        if (_context.Entry(entity).State == EntityState.Detached)
        {
            dbSet.Attach(entity);
        }

        _context.Entry(entity).State = EntityState.Modified;
    }

    public void Delete(T entity)
    {
        if (_context.Entry(entity).State == EntityState.Detached)
        {
            dbSet.Attach(entity);
        }

        dbSet.Remove(entity);
    }
}

Unit Of Work

The Unit of Work layer will make sure that when multiple repositories are used, the SaveChanges() method call will coordinate the database updates.

IUnitOfWork.cs:

public interface IUnitOfWork
{
    IRepository<Car> CarRepository { get; }
    IRepository<Owner> OwnerRepository { get; }
    IRepository<Service> ServiceRepository { get; }

    void Commit();
}

And the implementation of UnitOfWork.cs:

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private readonly ApplicationDbContext _context;

    private IRepository<Car> _carRepository;
    private IRepository<Owner> _ownerRepository;
    private IRepository<Service> _serviceRepository;

    public UnitOfWork(ApplicationDbContext context)
    {
        _context = context;
    }
    public IRepository<Car> CarRepository
    {
        get
        {
            if (_carRepository == null)
                _carRepository = new Repository<Car>(_context);

            return _carRepository;
        }
    }

    public IRepository<Owner> OwnerRepository
    {
        get
        {
            if (_ownerRepository == null)
                _ownerRepository = new Repository<Owner>(_context);

            return _ownerRepository;
        }
    }

    public IRepository<Service> ServiceRepository
    {
        get
        {
            if (_serviceRepository == null)
                _serviceRepository = new Repository<Service>(_context);

            return _serviceRepository;
        }
    }

    public void Commit()
    {
        _context.SaveChanges();
    }
}

DI configuration and usage from API client are identical to the repository project.

Benefits

  • Repository pattern provides an abstraction of your data so that the business logic can work with simple collections without being worried about the real data storage mechanisms

  • Testability at its best, as you can just make a fake object that implements the interface and that's it

  • Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers. Martin Fowler

  • With Generic Repository a good number of data-oriented applications can get up and running pretty quickly

Caveats and workarounds

  • With repositories you should always return IEnumerable instead of IQuerable, remember that the reason is to provide an isolated layer for queries and you don't want queries to bubble up to other application layers

  • Repositories deal with domain objects, not view models! What to do for the dashboard/statistics page? Implement some provider, suppose ServiceProvider.cs, and run there all your .AsNoTracking() queries there.

  • Mapping is a responsibility of the application layer, so keep mapper to your mvc/api/wcf project instead of the repository one.

Sample project

You can find the full code for this article at the following Github repository: Repository and Generic Repository patterns with Unit of Work

The solution is organized as the following:

-/ Domain (more like a bunch of POCO, however, domain modeling is outside of the scope for this article) contains the business entities that this app will work with

-/Infrastructure/Domain.EF.SqlServer contains the necessary configurations and database context

-/RepositoryPattern contains two projects: the implementation of the repository pattern with Unit of Work and a sample web api client for consuming it.

-/GenericRepositoryPattern contains two projects: The implementation of Generic Repository with Unit of work, and a sample web api client for consuming it.