C# — UnitOfWork And Repository Pattern

For data store insulation and test driven development

Kristoffer Karlsson
Feb 7, 2017 · 3 min read

Why the UnitOfWork and Repository pattern?

The UnitOfWork and repository patterns are intended to act like a abstraction layer between business logic and data access layer.
This can help insulate your application from changes in the data store and can facilitate automated unit testing / test driven development.
Source can be downloaded here.

pc >  Install-Package EntityFramework

Some Entities

[Table("Authors")]
public class Author {
[Key]
public int ID { get; set; }
public string Name { get; set; } public virtual ICollection<Book> Books { get; set; }
}
[Table("Books")]
public class Book {
[Key]
public int ID { get; set; }
public string Title { get; set; } public int Author_ID { get; set; }

[ForeignKey("Author_ID")]
public virtual Author Author { get; set; }
}

DbContext

public class MyDbContext : DbContext
{
public virtual DbSet<Author> Authors { get; set; }
public virtual DbSet<Book> Books { get; set; }
public MyDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}

The Generic Repository

public interface IRepository<T> where T : class
{
IQueryable<T> Entities { get; }
void Remove(T entity); void Add(T entity);
}
public class GenericRepository<T> : IRepository<T> where T : class
{
private readonly MyDbContext _dbContext;
private IDbSet<T> _dbSet => _dbContext.Set<T>();
public IQueryable<T> Entities => _dbSet;
public GenericRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public void Remove(T entity)
{
_dbSet.Remove(entity);
}
public void Add(T entity)
{
_dbSet.Add(entity);
}
}

The UnitOfWork

public interface IUnitOfWork
{
IRepository<Author> AuthorRepository { get; } IRepository<Book> BookRepository { get; }
/// <summary>
/// Commits all changes
/// </summary>
void Commit();
/// <summary>
/// Discards all changes that has not been commited
/// </summary>
void RejectChanges();
void Dispose();
}
public class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _dbContext;
#region Repositories public IRepository<Author> AuthorRepository =>
new GenericRepository<Author>(_dbContext);
public IRepository<Book> BookRepository =>
new GenericRepository<Book>(_dbContext);
#endregion public UnitOfWork(MyDbContext dbContext)
{
_dbContext = dbContext;
}
public void Commit()
{
_dbContext.SaveChanges();
}
public void Dispose()
{
_dbContext.Dispose();
}
public void RejectChanges()
{
foreach (var entry in _dbContext.ChangeTracker.Entries()
.Where(e => e.State != EntityState.Unchanged))
{
switch (entry.State)
{
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Modified:
case EntityState.Deleted:
entry.Reload();
break;
}
}
}
}

Usage

var dbContext = new MyDbContext("{connectionString goes here}");
var unitOfWork = new UnitOfWork(dbContext);
var books = unitOfWork.BookRepository.Entities
.Where(n => n.Title == "Hello World");
var justOneBook = unitOfWork.BookRepository.Entities
.First(n => n.ID == 1);
// Create
var author = new Author() { Name = "Kris" };
unitOfWork.AuthorRepository.Add(author);unitOfWork.Commit(); // Save changes to database
// Update
var author = unitOfWork.AuthorRepository.Entities
.First(n => n.Name == "Kris");
author.Name = "Dan";unitOfWork.Commit(); // Save changes to database
// Delete
var author = unitOfWork.AuthorRepository.Entities
.First(n => n.Name == "Dan");
unitOfWork.AuthorRepository.Remove(author);unitOfWork.Commit(); // Save changes to database
// Delete
var author = unitOfWork.AuthorRepository.Entities
.First(n => n.Name == "Dan");
unitOfWork.AuthorRepository.Remove(author);// Ops i don't want to do thisunitOfWork.RejectChanges(); // Reject all changes

Testing

You can easily write unit tests by simply mocking the DbContext methods. For example you could mock the Entities property to always return a fake list of objects. That way the DbContext won’t make any external calls to your data layer.

Source

Source can be downloaded here.

BTC

1CGu9Ctt1AuyXiWMJ2nEDoH1RRAKtStdjx

ETH

0xd2291b554075da7f61210db2648a7f0a2d006190

XRB

xrb_3fw49ebj4yfyzpxjeb474678hdgp1d69udctkgww4yzfhaa8oeye6o3sg98f

Kristoffer Karlsson

Written by

Software developer and indie game developer from Sweden. Been turning coffee into code since 2010.