A Generic Repository and Unit of Work Implementation for Entity Framework

You may be familiar with my previous posts describing the implementation of design patterns for Entity Framework.  It started with using generics for lookup tables, and followed with a generic CRUD repository.  This strategy follows along with our SSW rule, Do you use the Repository Pattern for data access.

In the lead up to SSW's recent Enterprise MVC course, I had lots of discussions with Adam Stephensen and Eric Phan about whether we could do a better job with this pattern.  We've come up with what we think is a great solution that includes the Unit of Work pattern as well.

We felt it was really important to separate the domain models, the repositories, and the context; a separation that's not made by Entity Framework. Doing so gave us some really important benefits.  Testing is made much easier.  For one, we can mock our context enabling us to test our repositories without going back to the database.  We can also mock the repositories to test the code that calls it.

A quick shout-out to Adrian Clark who suggested this separation in the comments last time.

We also used Inversion of Control to inject a context into the repository.  We could have had a reference to each of our repositories in our context, but inverting this relationship meant that we only needed to instantiate those repositories we were actually using.  It means less overhead and a looser coupling between the context and each repository.

First, we create our IUnitOfWork and our IGenericRepository.

[csharp] public interface IUnitOfWork : IDisposable
{ DbSet<TEntity> Set<TEntity>() where TEntity : class;

int SaveChanges();

} [/csharp] [csharp] public interface IGenericRepository<TEntity>
where TEntity : class { IQueryable<TEntity> Select();

IEnumerable<TEntity> GetAll();

IEnumerable<TEntity> Where(Func<TEntity, bool> predicate);

TEntity GetSingle(Func<TEntity, bool> predicate);

TEntity GetFirst(Func<TEntity, bool> predicate);

void Add(TEntity entity);

void Delete(TEntity entity);

void Attach(TEntity entity);

void Dispose(bool disposing);

void Dispose();

} [/csharp]

Then, we create an interface for our specific context. In this example, we have a context with a set of customers. This context is essentially a unit of work, so we'll inherit that interface.

[csharp] public interface ICustomerContext : IUnitOfWork
{ DbSet<Customer> Customers { get; set; } } [/csharp]

Next, we'll create a concrete GenericRepository instance. This is very similar to the version in my previous post, but take note of the repository that takes a context. We use this to inject our Unit of Work.

[csharp] public class GenericRepository<TContext, TEntity> : IGenericRepository<TEntity>
where TContext : IUnitOfWork where TEntity : class { protected TContext _context; /// <summary> /// Constructor that takes a context /// </summary> /// <param name="context">An established data context</param> public GenericRepository(TContext context) { _context = context; }

public IQueryable<TEntity> Select()
{
    return _context.Set<TEntity>().AsQueryable();
}

public IEnumerable<TEntity> GetAll()
{
    return _context.Set<TEntity>().AsEnumerable();
}

public IEnumerable<TEntity> Where(Func<TEntity, bool> predicate)
{
    return _context.Set<TEntity>().Where(predicate);
}

public TEntity GetSingle(Func<TEntity, bool> predicate)
{
    return _context.Set<TEntity>().Single(predicate);
}

public TEntity GetFirst(Func<TEntity, bool> predicate)
{
    return _context.Set<TEntity>().First(predicate);
}

public void Add(TEntity entity)
{
    if (entity == null)
        throw new ArgumentException("Cannot add a null entity");

    _context.Set<TEntity>().Add(entity);
}

public void Delete(TEntity entity)
{
    if (entity == null)
        throw new ArgumentException("Cannot delete a null entity");

    _context.Set<TEntity>().Remove(entity);
}

public void Attach(TEntity entity)
{
    if (entity == null)
        throw new ArgumentException("Cannot attach a null entity");

    _context.Set<TEntity>().Attach(entity);
}

#region IDisposable implementation
private bool disposedValue;

public void Dispose(bool disposing)
{
    if (!this.disposedValue)
    {
        if (disposing)
        {
            // dispose managed state here if required
        }
        // dispose unmanaged objects and set large fields to null
    }
    this.disposedValue = true;
}

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}
#endregion

}

[/csharp]

Next, we'll create a concretion for our context using Entity Framework. We'll inherit DbContext as well as implementing ICustomerContext, and we'll create both an empty constructor and one that takes a connection string so we can connect to a specific database.

[csharp] public class CustomerContext : System.Data.Entity.DbContext, ICustomerContext
{ public CustomerContext() { }

public CustomerContext(string connectionString) : base(connectionString) { }

} [/csharp]

And that's all we really need to do.

When we use it, we have to make sure we refer only to our interfaces so we can replace them later if need be.

[csharp] private ICustomerContext _context;
private IGenericRepository<Customer> _customerRepository;
[/csharp]

Ideally, we'd be using Dependency Injection to inject our concretions at runtime, but here's an example of how you would do it.

[csharp] context = new CustomerContext();
_customerRepository = new GenericRepository<ICustomerContext,Customer>(
context);
[/csharp]

From there, we can do whatever we need to do with the objects in our repository, and just call SaveChanges() on our context when we're done. For example:

[csharp] public ResetCustomerLastLoginDate(int customerId) {
var customer = _customerRepository.GetSingle(c => c.CustomerId == customerId); customer.LastLoginDate = DateTime.Now; _context.SaveChanges(); } [/csharp]

Damian Brady

I'm an Australian developer, speaker, and author specialising in DevOps, developer process, and software architecture. I love Octopus Deploy, Visual Studio Team Services, and reducing process waste.

Subscribe to Damian Brady

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!