Implementing UnitOfWork And Repository Pattern

Repository pattern is extensively used in Web API projects. In fact, it is a very popular architectural design pattern for desktop applications. There is another concept called ‘UnitOfWork’ which works with the repository pattern. Here, many of us make small mistakes while implementing UnitOfWork. Today, in this article, I will show you a nice way to implement ‘UnitOfWork’ with ‘Repository Pattern’. To explain the whole thing, we will make a very small Web API solution step by step. Also, I will explain a few tricks and tweaks that I personally do while designing any solution. Let’s get started.

Step 1 - Create a solution and add basic components

  • I have created a basic Web API project and added a few layers to the solution.

    Implementing UnitOfWork And Repository Pattern

  • UnitOfWorkRepositoryPatternDemo is the standard WebAPI project.
  • *.BusinessLogic is the layer that contains all the business logics.
  • *.Data is the layer to keep the edmx.
  • *.Models layer is the place for all the model classes. Each model class generally contains at least all the properties that are present in the corresponding entity class generated by EF.
  • *.Repository is the main component to focus on in this article. I will explain this layer in greater details.

Step 2 - Implement a repository layer

  • Repository layer will look like given below.

    Implementing UnitOfWork And Repository Pattern
  • First, create the IRepository interface inside ‘/Shared’ folder. I name it shared because this contains shared interfaces which can be shared across for Mocking.

  • The code for IRepository.cs will look like this.
    1. public interface IRepository<T> where T : class  
    2.     {  
    3.         // To get single item by its id  
    4.         T Get(object id);  
    5.   
    6.         // To get all items  
    7.         IEnumerable<T> GetAll();  
    8.   
    9.         // To search a set of items by passing expression as parameter  
    10.         IEnumerable<T> Find(Expression<Func<T, bool>> predicate);  
    11.   
    12.         // To add a single item  
    13.         void Add(T entity);  
    14.   
    15.         // To update an item  
    16.         void Update(T entity);  
    17.   
    18.         // To add a set of entities  
    19.         void AddRange(IEnumerable<T> entities);  
    20.   
    21.         //***  
    22.         // Returns result as Querayable such that you can apply more logic  
    23.         // on top of it  
    24.         IQueryable<T> Query(Expression<Func<T, bool>> predicate);  
    25.   
    26.         void Remove(T entity);  
    27.         void RemoveRange(IEnumerable<T> entities);  
    28.     }  
  • Carefully look at the method ‘Query’. This is the tweak I use for making it more flexible at the business layer. This method takes an expression as an argument. Please look at the below code block which is the implementation of this interface.

    Repository.cs
    1. public abstract class Repository<T> : IRepository<T> where T : class  
    2.     {  
    3.         protected readonly DbContext _context;  
    4.   
    5.         public Repository(DbContext context)  
    6.         {  
    7.             _context = context;  
    8.         }  
    9.   
    10.         public T Get(object id) => _context.Set<T>().Find(id);  
    11.   
    12.         public IEnumerable<T> GetAll() => _context.Set<T>().ToList();  
    13.   
    14.         public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)  
    15.         {  
    16.             return _context.Set<T>().Where(predicate);  
    17.         }  
    18.   
    19.         public void Add(T entity) => _context.Entry<T>(entity).State = EntityState.Added;  
    20.   
    21.         public void Remove(T entity)=> _context.Entry<T>(entity).State = EntityState.Deleted;  
    22.   
    23.         public void Update(T entity)=> _context.Entry<T>(entity).State = EntityState.Modified;  
    24.   
    25.         public void AddRange(IEnumerable<T> entities)  
    26.         {  
    27.             foreach (var entity in entities)  
    28.                 _context.Entry<T>(entity).State = EntityState.Added;  
    29.         }  
    30.   
    31.         public void RemoveRange(IEnumerable<T> entities)  
    32.         {  
    33.             foreach (var entity in entities)  
    34.                 _context.Entry<T>(entity).State = EntityState.Deleted;  
    35.         }  
    36.   
    37.         public IQueryable<T> FindAsQueryable()=> _context.Set<T>().AsQueryable<T>();  
    38.   
    39.         public IQueryable<T> Query(Expression<Func<T, bool>> predicate)  
    40.         {  
    41.             return _context.Set<T>().Where(predicate).AsQueryable();  
    42.         }  
    43.   
    44. }  
  • Please note, since Repository is a base class, I have made it abstract such that it can’t be instantiated separately.
  • This implementation is pretty straightforward. The DbContext class is present in System.Data.Entity namespace.
  • IEmployeeRepository looks like this.
    1. public interface IEmployeeRepository : IRepository<Employee>  
    2.     {  
    3.     }  
  • Similarly EmployeeRepository looks like this.
    1. public class EmployeeRepository : Repository<Employee>, IEmployeeRepository  
    2.     {  
    3.         public EmployeeRepository(DbContext _context) :  
    4.             base(_context)  
    5.         {            
    6.         }  
    7.     }  
  • Our Employee repository basic implementation is ready. The UnitOfWork is to unify all the repositories. It’s the main gateway to repositories. Let’s see how.

Step 3 - Implement UnitOfWork

  • First, have a look at the IUnitOfWork interface. It’s very simple.
    1. public interface IUnitOfWork : IDisposable  
    2.     {  
    3.         IEmployeeRepository Employees { get; }  
    4.         int Complete();  
    5.         int Complete(bool usingTransaction);  
    6.         IUnitOfWork New();  
    7.     }  
  • Once you see the implementation of IUnitOfWork, you will get the flavor of it.
    1. public class UnitOfWork : IUnitOfWork  
    2.     {  
    3.         private DbContext _context = null;  
    4.         private readonly Type ContextType;  
    5.         public UnitOfWork()  
    6.         {  
    7.             // Store the type  
    8.             ContextType = typeof(Data.EF.EmployeeDbEntities);  
    9.         }  
    10.   
    11.         private DbContextTransaction BeginTransaction()  
    12.         {  
    13.             return _context.Database.BeginTransaction();  
    14.         }  
    15.   
    16.         public IEmployeeRepository Employees  
    17.         {  
    18.             get  
    19.             {  
    20.                 return new EmployeeRepository(_context);  
    21.             }  
    22.         }  
    23.   
    24.         public int Complete()  
    25.         {  
    26.             return _context.SaveChanges();  
    27.         }  
    28.   
    29.         public int Complete(bool usingTransaction)  
    30.         {  
    31.             if (!usingTransaction)  
    32.                 return Complete();  
    33.   
    34.             int status = -1;  
    35.             using (var ts = _context.Database.BeginTransaction())  
    36.             {  
    37.                 try  
    38.                 {  
    39.                     status = _context.SaveChanges();  
    40.                     ts.Commit();  
    41.                 }  
    42.                 catch (Exception)  
    43.                 {  
    44.                     ts.Rollback();  
    45.                     throw;  
    46.                 }  
    47.             }  
    48.             return status;  
    49.         }  
    50.   
    51.         public void Dispose()  
    52.         {  
    53.             if (_context != null)  
    54.                 _context.Dispose();  
    55.         }  
    56.   
    57.         public IUnitOfWork New()  
    58.         {  
    59.             _context = null;  
    60.             TryCreateContext();  
    61.             return this;  
    62.         }  
    63.   
    64.         void TryCreateContext()  
    65.         {  
    66.             if (ContextType == default(Type))  
    67.                 throw new Exception("Context type Unknown");  
    68.   
    69.             if (_context == null)  
    70.                 _context = Activator.CreateInstance(ContextType) as DbContext;  
    71.         }  
    72. }  
  • Have a closer look. The constructor of UnitOfWork stores the type of DbContext instead of a new instance of DbContext. This is the tweak I apply to improve the performance. The reason is that when the EF DbContext constructor is invoked, there are many heavy sub-operations which happen behind the scenes. Also, there might be some business layer calls that might not need to access UnitOfWork. This thing will be clear once we look into the business logic layer.

  • Check the New() method which returns an instance of IUnitOfWork. This method actually creates an object of the type that we stored during construction. TryCreateContext() shows how it creates an object from the type using reflection. We will get to know the real benefit of it in the business layer.

Step 4 - Business layer implementation

  • In business layer, I have created a base class.
    1. public abstract class BaseLogic  
    2.     {  
    3.         protected readonly IUnitOfWork unitOfWork;  
    4.   
    5.         public BaseLogic(IUnitOfWork unitOfWork)  
    6.         {  
    7.             this.unitOfWork = unitOfWork;  
    8.         }  
    9. }  
  • Each business logic class will inherit from this base class.
  • Now let’s check the implementation of EmployeeLogic.cs
    1. public class EmployeeLogic : BaseLogic, Shared.IEmployeeLogic  
    2.     {  
    3.         public EmployeeLogic(IUnitOfWork uow) : base(uow)  
    4.         {  
    5.         }  
    6.   
    7.         public List<Employee> GetEmployees()  
    8.         {  
    9.             using(IUnitOfWork uow = base.unitOfWork.New())  
    10.             {  
    11.                 return uow.Employees.GetAll()  
    12.                     .Select(x => new Employee  
    13.                     {  
    14.                         Id = x.ID,  
    15.                         Name = x.Name  
    16.                     }).ToList();  
    17.             }     
    18.         }  
    19.   
    20.         public Employee SaveEmployee(Employee model)  
    21.         {  
    22.             var employeeEntity = new Entity.Employee() { Name = model.Name };  
    23.   
    24.             using(IUnitOfWork uow = base.unitOfWork.New()) {  
    25.   
    26.                 uow.Employees.Add(employeeEntity);  
    27.                 if(uow.Complete()>0)  
    28.                 {  
    29.                     model.Id = employeeEntity.ID;  
    30.                     return model;  
    31.                 }  
    32.             }  
    33.             throw new Exception("Operation failed");  
    34.         }   
    35.   
    36.         public void DoSomethingElseThatDoesntUseUnitOfWork()  
    37.         {  
    38.   
    39.         }  
    40.     }  
  • Just see, the constructor of EmployeeLogic is dependency injected with IUnitOfWork. And that has been passed to the base. In GetEmployees() and SaveEmployee(…) methods, a new UnitOfWork instance is created by base.unitOfWork.New() method. Thus, the object of UnitOfWork will be created only when you need it; you claim it. If you have any other method in a business layer which doesn’t make use of UnitOfWork object, it won’t create an unnecessary object while injecting dependency through the constructor.
  • Also, another benefit is, you can happily mock IUnitOfWork and inject it into business class. Thus, it improves testability and optimizes performance.
  • Note: dependency injection is not explained in this article since my main objective was to explain UnitOfWork and Repository pattern.
  • I am attaching the sample code as a zip. Please leave your questions in the comments section if you have any. Happy learning!


Similar Articles