The Unit of Work Pattern in C# is used to group one or more operations (usually database CRUD operations) into a single transaction and execute them by applying the principle of do everything or do nothing. That means if any of the operations in the transaction failed, then it will roll back the transaction. If all the operations are successful, then it will commit the transaction. So, it will execute all the database operations as one unit.
Problems
In the Repository Pattern, it is easy to realize that we have more than one repository, as in the example in part 1 we have 3 repositories (CustomerRepository, OrderRepository and ProductRepository). If these 3 guys work separately, ie editing data each guy does not affect the other guy, we don't talk about it, but in this case Order and Customer are related. When we add 1 Order, we will first check if the Customer list is available or not? if yes then we will get that Customer from CustomerRepository otherwise we will have to create a new instance.
publicIActionResultCreate(CreateOrderModel model){if (!model.LineItems.Any()) returnBadRequest("Please submit line items");if (string.IsNullOrWhiteSpace(model.Customer.Name)) returnBadRequest("Customer needs a name");var customer =CustomerRepository.Find(c =>c.Name==model.Customer.Name).FirstOrDefault();if (customer !=null) {customer.ShippingAddress=model.Customer.ShippingAddress;customer.PostalCode=model.Customer.PostalCode;customer.City=model.Customer.City;customer.Country=model.Customer.Country;customerRepository.Update(customer);customerRepository.SaveChanges(); }else { customer =newCustomer { Name =model.Customer.Name, ShippingAddress =model.Customer.ShippingAddress, City =model.Customer.City, PostalCode =model.Customer.PostalCode, Country =model.Customer.Country }; }var order =newOrder { LineItems =model.LineItems .Select(line =>newLineItem { ProductId =line.ProductId, Quantity =line.Quantity }) .ToList(), Customer = customer };orderRepository.Add(order);orderRepository.SaveChanges();returnOk("Order Created");}
As the above example in the same function, we have to call the database up to 4 times, actually both customerRepository and orderRepository are dbContext in EntityFramework but they are 2 different instances leading to having to call the database many times for the update. of each guy. So to improve efficiency, why not create a class containing all these guys, when you change something, only update it once.
Design
First we need to create a UnitOfWork class that will hold all the repositories we have. This class I wrote in the Infrastructure layer together with the repositories
The IUnitOfWork class is an interface that includes the repositories, this way instead of injecting each repository where it needs to be used, just inject this UnitOfWork class and every time it needs to be used, it will initialize the instance for that repository. As mentioned, only when called, we will create an instance for it and have an extra SaveChange function, when calling this function, it will simultaneously update all Repository, this function is provided by Enity Framework, you guys don't have to wonder. Why can't you do it?
After creating the UnitOfWork class, we inject it where we need to use it, for example in OrderController or because we have to inject multiple repository instances, just inject this class to be able to use all the repositories.
And using it is also very simple, just add the prefix unitOfWork before the original functions and use it without any further editing.
publicIActionResultIndex(){var orders =unitOfWork.OrderRepository.Find(order =>order.OrderDate>DateTime.UtcNow.AddDays(-1));returnView(orders);}publicIActionResultCreate(){var products =unitOfWork.ProductRepository.All();returnView(products);}[HttpPost]publicIActionResultCreate(CreateOrderModel model){if (!model.LineItems.Any()) returnBadRequest("Please submit line items");if (string.IsNullOrWhiteSpace(model.Customer.Name)) returnBadRequest("Customer needs a name");var customer =unitOfWork.CustomerRepository .Find(c =>c.Name==model.Customer.Name) .FirstOrDefault();if(customer !=null) {customer.ShippingAddress=model.Customer.ShippingAddress;customer.PostalCode=model.Customer.PostalCode;customer.City=model.Customer.City;customer.Country=model.Customer.Country;unitOfWork.CustomerRepository.Update(customer); }else { customer =newCustomer { Name =model.Customer.Name, ShippingAddress =model.Customer.ShippingAddress, City =model.Customer.City, PostalCode =model.Customer.PostalCode, Country =model.Customer.Country }; }var order =newOrder { LineItems =model.LineItems .Select(line =>newLineItem { ProductId =line.ProductId, Quantity =line.Quantity }) .ToList(), Customer = customer };unitOfWork.OrderRepository.Add(order);unitOfWork.SaveChanges();returnOk("Order Created");}
Comparing the Create function in OrderController now with the original, we see that the number of calls to the database is significantly reduced, this is the benefit of applying the Unit Of Work Pattern in the project.