Repository And UnitOfWork Pattern - Part Two

In the previous article part 1 we looked at the Repository Pattern that provides an ability to create repository class which holds the data of specific entity or entities in the form of collections and also used to create an abstraction layer between the data persistence (data access layer) and the business logic (business access layer) to perform operations on the data persistence.

UnitOfWork

In the previous article, Repository And UnitOfWork Pattern - Part Onewe looked at the Repository Pattern, which provides the ability to create repository class which holds the data of specific entity or entities in the form of collections. It's also used to create an abstraction layer between the data persistence (data access layer) and the business logic (business access layer) to perform operations on the data persistence. 

In the previous article, we created a repository class for data table Order and OrderItem, in which each repository class created an object of DataContext which further saved the changes into the data persistence. We also discussed the problem with the repository pattern, which is that we need to have data all saved in one single transaction instead of having a separate DataContext object in every repository.  

UnitOfWork (UOW) is the common pattern that is used to resolve data concurrency issues which arise when each repository implements and maintains separate Data context objects. The UnitOfWork (UOW) pattern implementation manages in-memory database operations on entities as one transaction. So, if one of the operations is failing, then the entire database operations will rollback.

According to Martin Fowler, a unit of work "maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems." The "list of objects" are the repositories, the "business transactions" are the repository-specific business rules to retrieve data, and the "coordination of the writing of changes and concurrency problems" is through the DbContext.

Now, let’s have a look at the implementation of Repository pattern example using UnitOfWork Pattern.

Make the following changes to the BaseRepository class,

  • First, remove the instance of OrderManagementDbContext.
  • Remove Context.SaveChanges(); call from operation 
  • Add a parameter of type DbContext to the constructor
  1. public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class  
  2. {  
  3.     protected DbSet<TEntity> Entities;  
  4.     private readonly DbContext _dbContext;  
  5.   
  6.     /// <summary>  
  7.     /// Initializes a new instance of the <see cref="BaseRepository{TEntity}"/> class.  
  8.     /// Note that here I've stored Context.Set<TEntity>() in the constructor and store it in a private field like _entities.   
  9.     /// This way, the implementation  of our methods would be cleaner:        ///   
  10.     /// _entities.ToList();  
  11.     /// _entities.Where();  
  12.     /// _entities.SingleOrDefault();  
  13.     /// </summary>  
  14.     public BaseRepository(DbContext dbContext)  
  15.     {  
  16.         _dbContext = dbContext;  
  17.         Entities = _dbContext.Set<TEntity>();  
  18.     }  
  19.   
  20.     public virtual IEnumerable<TEntity> Get(  
  21.         Expression<Func<TEntity, bool>> filter = null,  
  22.         Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,  
  23.         string includeProperties = "")  
  24.     {  
  25.         IQueryable<TEntity> query = Entities;  
  26.   
  27.         if (filter != null)  
  28.         {  
  29.             query = query.Where(filter);  
  30.         }  
  31.   
  32.         foreach (var includeProperty in includeProperties.Split  
  33.             (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))  
  34.         {  
  35.             query = query.Include(includeProperty);  
  36.         }  
  37.   
  38.         if (orderBy != null)  
  39.         {  
  40.             return orderBy(query).ToList();  
  41.         }  
  42.         else  
  43.         {  
  44.             return query.ToList();  
  45.         }  
  46.     }  
  47.   
  48.   
  49.     /// <summary>  
  50.     /// Gets the specified identifier.  
  51.     /// </summary>  
  52.     /// <param name="id">The identifier.</param>  
  53.     /// <returns></returns>  
  54.     public virtual TEntity Get(int id)  
  55.     {  
  56.         // Here we are working with a DbContext, not specific DbContext.   
  57.         // So we don't have DbSets we need to use the generic Set() method to access them.  
  58.         return Entities.Find(id);  
  59.     }  
  60.   
  61.     /// <summary>  
  62.     /// Gets all.  
  63.     /// </summary>  
  64.     /// <returns></returns>  
  65.     public IEnumerable<TEntity> GetAll()  
  66.     {  
  67.         return Entities.ToList();  
  68.     }  
  69.   
  70.     /// <summary>  
  71.     /// Finds the specified predicate.  
  72.     /// </summary>  
  73.     /// <param name="predicate">The predicate.</param>  
  74.     /// <returns></returns>  
  75.     public IEnumerable<TEntity> Find(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)  
  76.     {  
  77.         return Entities.Where(predicate);  
  78.     }  
  79.   
  80.     /// <summary>  
  81.     /// Singles the or default.  
  82.     /// </summary>  
  83.     /// <param name="predicate">The predicate.</param>  
  84.     /// <returns></returns>  
  85.     public TEntity SingleOrDefault(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)  
  86.     {  
  87.         return Entities.Where(predicate).SingleOrDefault();  
  88.     }  
  89.   
  90.     /// <summary>  
  91.     /// First the or default.  
  92.     /// </summary>  
  93.     /// <returns></returns>  
  94.     public TEntity FirstOrDefault()  
  95.     {  
  96.         return Entities.SingleOrDefault();  
  97.     }  
  98.   
  99.     /// <summary>  
  100.     /// Adds the specified entity.  
  101.     /// </summary>  
  102.     /// <param name="entity">The entity.</param>  
  103.     public void Add(TEntity entity)  
  104.     {  
  105.         Entities.Add(entity);  
  106.     }  
  107.   
  108.     /// <summary>  
  109.     /// Adds the range.  
  110.     /// </summary>  
  111.     /// <param name="entities">The entities.</param>  
  112.     public void AddRange(IEnumerable<TEntity> entities)  
  113.     {  
  114.         Entities.AddRange(entities);  
  115.     }  
  116.   
  117.     /// <summary>  
  118.     /// Removes the specified entity.  
  119.     /// </summary>  
  120.     /// <param name="entity">The entity.</param>  
  121.     public void Remove(TEntity entity)  
  122.     {  
  123.         Entities.Remove(entity);  
  124.     }  
  125.   
  126.     /// <summary>  
  127.     /// Removes the range.  
  128.     /// </summary>  
  129.     /// <param name="entities">The entities.</param>  
  130.     public void RemoveRange(IEnumerable<TEntity> entities)  
  131.     {  
  132.         Entities.RemoveRange(entities);  
  133.     }  
  134.   
  135.   
  136.     /// <summary>  
  137.     /// Removes the Entity  
  138.     /// </summary>  
  139.     /// <param name="entityToDelete"></param>  
  140.     public virtual void RemoveEntity(TEntity entityToDelete)  
  141.     {  
  142.         if (_dbContext.Entry(entityToDelete).State == EntityState.Detached)  
  143.         {  
  144.             Entities.Attach(entityToDelete);  
  145.         }  
  146.         Entities.Remove(entityToDelete);  
  147.           
  148.     }  
  149.   
  150.     /// <summary>  
  151.     /// Update the Entity  
  152.     /// </summary>  
  153.     /// <param name="entityToUpdate"></param>  
  154.     public virtual void UpdateEntity(TEntity entityToUpdate)  
  155.     {  
  156.         Entities.Attach(entityToUpdate);  
  157.         _dbContext.Entry(entityToUpdate).State = EntityState.Modified;  
  158.          
  159.     }  
  160. }  

Add UnitOfWork class comprises the property for each of the repositories and an instance of data context object that will be passed to the different repositories, so that repository will have access to the same data context object instead of creating separate instance objects within the repository itself. UnitOfWork class also implements interface IDisposable that provides an ability to release memory as soon as the execution is over. 

  1. public interface IUnitOfWork : IDisposable  
  2. {  
  3.     bool SaveChanges();  
  4. }  
  5.   
  6. public class UnitOfWork : IUnitOfWork  
  7. {  
  8.     protected readonly OrderManagementDbContext Context;  
  9.   
  10.     public UnitOfWork()  
  11.     {  
  12.         Context = new OrderManagementDbContext();  
  13.     }  
  14.   
  15.     public bool SaveChanges()  
  16.     {  
  17.         bool returnValue = true;  
  18.         using (var dbContextTransaction = Context.Database.BeginTransaction())  
  19.         {  
  20.             try  
  21.             {  
  22.                 Context.SaveChanges();  
  23.                 dbContextTransaction.Commit();  
  24.             }  
  25.             catch (Exception)  
  26.             {  
  27.                 //Log Exception Handling message                      
  28.                 returnValue = false;  
  29.                 dbContextTransaction.Rollback();  
  30.             }  
  31.         }  
  32.   
  33.         return returnValue;  
  34.     }  
  35.  
  36.     #region Public Properties  
  37.   
  38.     private OrderRepository _orderRepository;  
  39.   
  40.     public OrderRepository OrderRepoistory => _orderRepository ?? (_orderRepository = new OrderRepository(Context));  
  41.   
  42.     private OrderItemRepository _orderItemRepository;  
  43.       
  44.     public OrderItemRepository OrderItemRepoistory => _orderItemRepository ?? (_orderItemRepository = new OrderItemRepository(Context));  
  45.  
  46.     #endregion  
  47.  
  48.  
  49.     #region IDisposable Support  
  50.     private bool _disposedValue = false// To detect redundant calls  
  51.   
  52.     protected virtual void Dispose(bool disposing)  
  53.     {  
  54.         if (_disposedValue) return;  
  55.   
  56.         if (disposing)  
  57.         {  
  58.             //dispose managed state (managed objects).  
  59.         }  
  60.   
  61.         // free unmanaged resources (unmanaged objects) and override a finalizer below.  
  62.         // set large fields to null.  
  63.   
  64.         _disposedValue = true;  
  65.     }  
  66.   
  67.     // override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.  
  68.     // ~UnitOfWork() {  
  69.     //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.  
  70.     //   Dispose(false);  
  71.     // }  
  72.   
  73.     // This code added to correctly implement the disposable pattern.  
  74.     public void Dispose()  
  75.     {  
  76.         // Do not change this code. Put cleanup code in Dispose(bool disposing) above.  
  77.         Dispose(true);  
  78.         // uncomment the following line if the finalizer is overridden above.  
  79.         // GC.SuppressFinalize(this);  
  80.     }  
  81.     #endregion  
  82.   
  83. }  
Now, look at the main program which creates an object of UnitOfWork and performs different operations on different repositories as a single transaction.
  1. class Program  
  2. {  
  3.     static void Main(string[] args)  
  4.     {  
  5.         GetOrders();  
  6.         AddOrder();  
  7.         GetOrders();  
  8.   
  9.         Console.ReadLine();  
  10.     }  
  11.   
  12.     private static void AddOrder()  
  13.     {  
  14.         Order order = new Order  
  15.         {  
  16.             OrderId = Guid.NewGuid().ToString(),  
  17.             OrderDate = DateTime.Now,  
  18.             OrderStatus = "In Process"  
  19.         };  
  20.   
  21.         using (var unitOfWork = new UnitOfWork())  
  22.         {  
  23.             unitOfWork.OrderRepository.Add(order);  
  24.   
  25.   
  26.             order.OrderItems = new List<OrderItem>  
  27.             {  
  28.                 new OrderItem  
  29.                 {  
  30.                     OrderId = order.OrderId,  
  31.                     ItemName = "Item 1",  
  32.                     OrderItemId = Guid.NewGuid().ToString(),  
  33.                     Quantity = 1  
  34.                 },  
  35.                 new OrderItem  
  36.                 {  
  37.                     OrderId = order.OrderId,  
  38.                     ItemName = "Item 2",  
  39.                     OrderItemId = Guid.NewGuid().ToString(),  
  40.                     Quantity = 4  
  41.                 }  
  42.             };  
  43.   
  44.             unitOfWork.OrderItemRepository.AddRange(order.OrderItems);  
  45.             unitOfWork.SaveChanges();  
  46.         }  
  47.     }  
  48.   
  49.     private static void GetOrders()  
  50.     {  
  51.   
  52.         using (var unitOfWork = new UnitOfWork())  
  53.         {  
  54.             var orders = unitOfWork.OrderRepository.GetAll();  
  55.   
  56.             IEnumerable<Order> orderDatas = orders.ToList();  
  57.             if (!orderDatas.Any())  
  58.             {  
  59.                 Console.WriteLine("No Order Placed");  
  60.             }  
  61.             else  
  62.             {  
  63.                 foreach (var orderData in orderDatas)  
  64.                 {  
  65.                     Console.WriteLine("Order {0} placed and currently is {1}", orderData.OrderId,  
  66.                         orderData.OrderStatus);  
  67.   
  68.                     var orderItems = unitOfWork.OrderItemRepository.Find(x => x.OrderId == orderData.OrderId);  
  69.                     IEnumerable<OrderItem> items = orderItems as OrderItem[] ?? orderItems.ToArray();  
  70.                     if (!items.Any()) continue;  
  71.                     Console.WriteLine("Items included in the order:");  
  72.                     foreach (var orderItem in items)  
  73.                     {  
  74.                         Console.WriteLine(orderItem.ItemName);  
  75.                     }  
  76.                 }  
  77.             }  
  78.         }  
  79.     }  
  80. }  
Output
 
Repository and UnitOfWork Pattern

 Repository and UnitOfWork Pattern
 
 Repository and UnitOfWork Pattern
 
As we can see in the above output, the data is inserted into both tables, i.e., Order and OrderItem. Now, make a change in the order Item data to insert the same item again.
  1. order.OrderItems = new List<OrderItem>  
  2. {  
  3.     new OrderItem  
  4.     {  
  5.         OrderId = order.OrderId,  
  6.         ItemName = "Item 1",  
  7.         OrderItemId = Guid.NewGuid().ToString(),  
  8.         Quantity = 1  
  9.     },  
  10.     new OrderItem  
  11.     {  
  12.         OrderId = order.OrderId,  
  13.         ItemName = "Item 1",  
  14.         OrderItemId = Guid.NewGuid().ToString(),  
  15.         Quantity = 4  
  16.     }  
  17. };  
Run the main program. We get to see the below output.
 
 Repository and UnitOfWork Pattern
 
 Repository and UnitOfWork Pattern
 
 Repository and UnitOfWork Pattern

As we can see, no data is inserted into either of the tables. So we can conclude that the UnitOfWork pattern allows us to handle multiple activities in a single transaction.

Summary

UnitOfWork pattern helps us in implementing the in-memory database operations and later saves in-memory updates as one transaction into the database.