UnitOfWork
In the previous article, Repository And UnitOfWork Pattern - Part One, we 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
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.
- public interface IUnitOfWork : IDisposable
- {
- bool SaveChanges();
- }
-
- public class UnitOfWork : IUnitOfWork
- {
- protected readonly OrderManagementDbContext Context;
-
- public UnitOfWork()
- {
- Context = new OrderManagementDbContext();
- }
-
- public bool SaveChanges()
- {
- bool returnValue = true;
- using (var dbContextTransaction = Context.Database.BeginTransaction())
- {
- try
- {
- Context.SaveChanges();
- dbContextTransaction.Commit();
- }
- catch (Exception)
- {
-
- returnValue = false;
- dbContextTransaction.Rollback();
- }
- }
-
- return returnValue;
- }
-
- #region Public Properties
-
- private OrderRepository _orderRepository;
-
- public OrderRepository OrderRepoistory => _orderRepository ?? (_orderRepository = new OrderRepository(Context));
-
- private OrderItemRepository _orderItemRepository;
-
- public OrderItemRepository OrderItemRepoistory => _orderItemRepository ?? (_orderItemRepository = new OrderItemRepository(Context));
-
- #endregion
-
-
- #region IDisposable Support
- private bool _disposedValue = false;
-
- protected virtual void Dispose(bool disposing)
- {
- if (_disposedValue) return;
-
- if (disposing)
- {
-
- }
-
-
-
-
- _disposedValue = true;
- }
-
-
-
-
-
-
-
-
- public void Dispose()
- {
-
- Dispose(true);
-
-
- }
- #endregion
-
- }
Now, look at the main program which creates an object of UnitOfWork and performs different operations on different repositories as a single transaction.
- class Program
- {
- static void Main(string[] args)
- {
- GetOrders();
- AddOrder();
- GetOrders();
-
- Console.ReadLine();
- }
-
- private static void AddOrder()
- {
- Order order = new Order
- {
- OrderId = Guid.NewGuid().ToString(),
- OrderDate = DateTime.Now,
- OrderStatus = "In Process"
- };
-
- using (var unitOfWork = new UnitOfWork())
- {
- unitOfWork.OrderRepository.Add(order);
-
-
- order.OrderItems = new List<OrderItem>
- {
- new OrderItem
- {
- OrderId = order.OrderId,
- ItemName = "Item 1",
- OrderItemId = Guid.NewGuid().ToString(),
- Quantity = 1
- },
- new OrderItem
- {
- OrderId = order.OrderId,
- ItemName = "Item 2",
- OrderItemId = Guid.NewGuid().ToString(),
- Quantity = 4
- }
- };
-
- unitOfWork.OrderItemRepository.AddRange(order.OrderItems);
- unitOfWork.SaveChanges();
- }
- }
-
- private static void GetOrders()
- {
-
- using (var unitOfWork = new UnitOfWork())
- {
- var orders = unitOfWork.OrderRepository.GetAll();
-
- IEnumerable<Order> orderDatas = orders.ToList();
- if (!orderDatas.Any())
- {
- Console.WriteLine("No Order Placed");
- }
- else
- {
- foreach (var orderData in orderDatas)
- {
- Console.WriteLine("Order {0} placed and currently is {1}", orderData.OrderId,
- orderData.OrderStatus);
-
- var orderItems = unitOfWork.OrderItemRepository.Find(x => x.OrderId == orderData.OrderId);
- IEnumerable<OrderItem> items = orderItems as OrderItem[] ?? orderItems.ToArray();
- if (!items.Any()) continue;
- Console.WriteLine("Items included in the order:");
- foreach (var orderItem in items)
- {
- Console.WriteLine(orderItem.ItemName);
- }
- }
- }
- }
- }
- }
Output
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.
- order.OrderItems = new List<OrderItem>
- {
- new OrderItem
- {
- OrderId = order.OrderId,
- ItemName = "Item 1",
- OrderItemId = Guid.NewGuid().ToString(),
- Quantity = 1
- },
- new OrderItem
- {
- OrderId = order.OrderId,
- ItemName = "Item 1",
- OrderItemId = Guid.NewGuid().ToString(),
- Quantity = 4
- }
- };
Run the main program. We get to see the below output.
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.