Back to: Design Patterns in C# With Real-Time Examples
Unit of Work in Repository Pattern with an Example
In this article, I will discuss How to implement the Unit Of Work in the Repository Pattern in C# with Examples. The unit of work in C# manages in-memory database CRUD operations on entities as one transaction. In simple words, we can say that if we want to implement Transactions while using Entity Framework and Repository Design Patterns, then we need to use the unit of work. So, if one of the operations fails as part of the transaction, the entire database operations will be rolled back. That means either all the database operations succeeded or none of them.
The Unit of Work in C# is the concept that is related to the effective implementation of the Repository Design Pattern. So, to understand this concept, it is important to understand the concept of the Repository Design Pattern in C#. So, I strongly recommend you read the following three articles if you have not yet read them before proceeding to this article.
- Non-Generic or Basic Repository Design Pattern.
- Generic Repository Design Pattern.
- Using both Generic and Non-Generic Repository Patterns.
What is a Repository?
As previously discussed, a repository is a class defined for an entity with all the possible database operations. For example, a repository for an Employee entity will have the basic CRUD operations and any other possible operations related to the Employee entity. The Repository Pattern can be implemented in two ways, i.e., using One Repository Per Entity and One Repository for all Entities. It is also possible to include both of them in our application.
One Repository Per Entity (Non-Generic Repository):
In this case, we must create a separate repository for each entity. For example, if we have two entities, Employee and Customer, in our application, we must create two repositories. The Employee Repository will have operations related to the Employee Entity, and the Customer Repository will only have operations related to the Customer Entity.
Generic Repository (One Repository for All Entities):
A Generic Repository can be used for all entities. In other words, the Generic Repository can be used for Employee Entity, Customer Entity, or any other entity. So, all the standard operations of all the entities will be put inside the Generic Repository.
Note: We use both Generic and Non-Generic Repositories in most Real-Time Applications. The Generic Repository contains the methods that are common for all the entities. But if you want some specific operation for some specific entities. Then, you need to create a specific repository with the required operations.
Unit of Work in C# Repository Pattern
The Unit of Work Pattern in C# groups one or more operations (usually database CRUD operations) into a single transaction and executes them by applying the principle of do everything or do nothing. That means if any of the transaction’s operations fail, 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.
We will work with the same example we used in our previous article. Below is the code snippet for the Generic Repository class.
using RepositoryUsingEFinMVC.DAL; using System.Collections.Generic; using System.Data.Entity; using System.Linq; namespace RepositoryUsingEFinMVC.GenericRepository { public class GenericRepository<T> : IGenericRepository<T> where T : class { public EmployeeDBContext _context = null; public DbSet<T> table = null; public GenericRepository() { this._context = new EmployeeDBContext(); table = _context.Set<T>(); } public GenericRepository(EmployeeDBContext _context) { this._context = _context; table = _context.Set<T>(); } public IEnumerable<T> GetAll() { return table.ToList(); } public T GetById(object id) { return table.Find(id); } public void Insert(T obj) { table.Add(obj); } public void Update(T obj) { table.Attach(obj); _context.Entry(obj).State = EntityState.Modified; } public void Delete(object id) { T existing = table.Find(id); table.Remove(existing); } public void Save() { _context.SaveChanges(); } } }
Problem with the above Implementation Code:
The issue arises when we are working with multiple repositories. For example, if we are working with two repositories, let us say Employee and Product Repositories, then in that case, both repositories will generate and maintain their own instance of the DbContext class. This may lead to issues in the future since each DbContext object will have its own in-memory list of changes in the records, i.e., maintaining the state of the entities, i.e., Added/Modified/Deleted. In such a case, if the SaveChanges method of one of the repositories fails and the SaveChanges method of the other one succeeds, it will result in database inconsistency. In our example, while adding data for the Employee and Product entities, both will use their own DbContext instance. Without the Unit of work, this situation can be represented in the following diagram.
This is where the concept of UnitOfWork is playing an important role.
Solution To the above Problem:
We will add another layer or intermediate between the Controller and the Generic/Non-Generic Repository to avoid this. This layer will act as a centralized store for all the repositories to receive the instance of the DbContext. This will ensure that, for a unit of transaction that spans across multiple repositories, it should either be complete for all entities or should fail entirely, as all of them will share the same instance of the DbContext. In our example, while adding data for the Employee and Product entities in a single transaction, both will use the same DbContext instance. This situation, with the Unit of work, can be represented in the following diagram.
In the above representation, during a single operation involving Employee and Product entities, both use the same DbContext instance. This will ensure that the other is not saved even if one breaks, thus maintaining database consistency. So, when SaveChanges is executed, it will be done for both repositories.
Unit Of Work in C#
In the Repository Pattern, the Unit of Work (UoW) is a design pattern used to manage transactions and ensure data consistency in your application when working with a database. It helps in coordinating multiple database operations within a single transaction scope. The key idea behind the Unit of Work pattern is to treat a group of related database operations as a single unit or transaction. This means that either all the operations within the unit succeed or they all fail. This ensures data integrity and consistency.
Here’s how the Unit of Work pattern typically works:
- UnitOfWork Interface/Class:
- Repository Interface/Class:
- UnitOfWork Implementation:
- Repository Implementation:
- Client Code:
UnitOfWork Interface/Class:
You create a Unit of Work interface or class that defines methods for managing transactions and repositories. The Unit of Work is responsible for tracking changes and coordinating the repositories.
Add a folder with the name UnitOfWork within the project. Once the folder is added, add one interface with the name IUnitOfWork.cs within the UnitOfWork folder and copy and paste the below code into it. As you can see in the code below, we are declaring the Operations which are required to implement the Unit Of Work, like Creating the Context Object, Creating or Starting the Transaction, Committing the Transaction, Roll Backing the Transaction, and the SaveChanges method which will make the changes permanent in the database.
using System.Data.Entity; namespace RepositoryUsingEFinMVC.UnitOfWork { public interface IUnitOfWork<out TContext> where TContext : DbContext, new() { //The following Property is going to hold the context object TContext Context { get; } //Start the database Transaction void CreateTransaction(); //Commit the database Transaction void Commit(); //Rollback the database Transaction void Rollback(); //DbContext Class SaveChanges method void Save(); } }
UnitOfWork Implementation
Implement the Unit of Work interface/class, which includes methods for starting, committing, and rolling back transactions. It also tracks changes made to entities during the transaction.
Add a class file named UnitOfWork.cs within the UnitOfWork folder and copy and paste the following code. The following class will implement the IUnitOfWork interface and provide implementations for the interface members. The following Generic UnitOfWork class code is self-explained, so please go through the comment lines for a better understanding.
using System; using System.Data.Entity; using System.Data.Entity.Validation; namespace RepositoryUsingEFinMVC.UnitOfWork { //Generic UnitOfWork Class. //While Creating an Instance of the UnitOfWork object, we need to specify the actual type for the TContext Generic Type //In our example, TContext is going to be EmployeeDBContext //new() constraint will make sure that this type is going to be a non-abstract type with a parameterless constructor public class UnitOfWork<TContext> : IUnitOfWork<TContext>, IDisposable where TContext : DbContext, new() { private bool _disposed; private string _errorMessage = string.Empty; //The following Object is going to hold the Transaction Object private DbContextTransaction _objTran; //Using the Constructor we are initializing the Context Property which is declared in the IUnitOfWork Interface //This is nothing but we are storing the DBContext (EmployeeDBContext) object in Context Property public UnitOfWork() { Context = new TContext(); } //The Dispose() method is used to free unmanaged resources like files, //database connections etc. at any time. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } //The Context property will return the DBContext object i.e. (EmployeeDBContext) object //This Property is declared inside the Parent Interface and Initialized through the Constructor public TContext Context { get; } //The CreateTransaction() method will create a database Transaction so that we can do database operations //by applying do everything and do nothing principle public void CreateTransaction() { //It will Begin the transaction on the underlying store connection _objTran = Context.Database.BeginTransaction(); } //If all the Transactions are completed successfully then we need to call this Commit() //method to Save the changes permanently in the database public void Commit() { //Commits the underlying store transaction _objTran.Commit(); } //If at least one of the Transaction is Failed then we need to call this Rollback() //method to Rollback the database changes to its previous state public void Rollback() { //Rolls back the underlying store transaction _objTran.Rollback(); //The Dispose Method will clean up this transaction object and ensures Entity Framework //is no longer using that transaction. _objTran.Dispose(); } //The Save() Method Implement DbContext Class SaveChanges method //So whenever we do a transaction we need to call this Save() method //so that it will make the changes in the database permanently public void Save() { try { //Calling DbContext Class SaveChanges method Context.SaveChanges(); } catch (DbEntityValidationException dbEx) { foreach (var validationErrors in dbEx.EntityValidationErrors) { foreach (var validationError in validationErrors.ValidationErrors) { _errorMessage = _errorMessage + $"Property: {validationError.PropertyName} Error: {validationError.ErrorMessage} {Environment.NewLine}"; } } throw new Exception(_errorMessage, dbEx); } } //Disposing of the Context Object protected virtual void Dispose(bool disposing) { if (!_disposed) if (disposing) Context.Dispose(); _disposed = true; } } }
Repository Interface
You create repository interfaces or classes for each entity type you want to work with. These repositories provide methods for performing CRUD (Create, Read, Update, Delete) operations on the entities. Next, Modify the IGenericRepository.cs file as shown below:
using System.Collections.Generic; namespace RepositoryUsingEFinMVC.GenericRepository { //Here, we are creating the IGenericRepository interface as a Generic Interface //Here, we are applying the Generic Constraint //The constraint is T which is going to be a class public interface IGenericRepository<T> where T : class { IEnumerable<T> GetAll(); T GetById(object id); void Insert(T obj); void Update(T obj); void Delete(T obj); } }
Repository Implementation:
Implement the repository interfaces/classes. These implementations should use the Unit of Work to coordinate database operations. Next, Modify the GenericRepository.cs class file as shown below:
using RepositoryUsingEFinMVC.DAL; using RepositoryUsingEFinMVC.UnitOfWork; using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Validation; using System.Linq; namespace RepositoryUsingEFinMVC.GenericRepository { public class GenericRepository<T> : IGenericRepository<T>, IDisposable where T : class { private IDbSet<T> _entities; private string _errorMessage = string.Empty; private bool _isDisposed; //While Creating an Instance of GenericRepository, we need to pass the UnitOfWork instance //That UnitOfWork instance contains the Context Object that our GenericRepository is going to use public GenericRepository(IUnitOfWork<EmployeeDBContext> unitOfWork) : this(unitOfWork.Context) { } //If you don't want to use Unit of Work, then use the following Constructor //which takes the context Object as a parameter public GenericRepository(EmployeeDBContext context) { //Initialize _isDisposed to false and then set the Context Object _isDisposed = false; Context = context; } //The following Property is going to return the Context Object public EmployeeDBContext Context { get; set; } //The following Property is going to set and return the Entity protected virtual IDbSet<T> Entities { get { return _entities ?? (_entities = Context.Set<T>()); } } //The following Method is going to Dispose of the Context Object public void Dispose() { if (Context != null) Context.Dispose(); _isDisposed = true; } //Return all the Records from the Corresponding Table public virtual IEnumerable<T> GetAll() { return Entities.ToList(); } //Return a Record from the Coresponding Table based on the Primary Key public virtual T GetById(object id) { return Entities.Find(id); } //The following Method is going to Insert a new entity into the table public virtual void Insert(T entity) { try { if (entity == null) { throw new ArgumentNullException("Entity"); } if (Context == null || _isDisposed) { Context = new EmployeeDBContext(); } Entities.Add(entity); //commented out call to SaveChanges as Context save changes will be //called with Unit of work //Context.SaveChanges(); } catch (DbEntityValidationException dbEx) { HandleUnitOfWorkException(dbEx); throw new Exception(_errorMessage, dbEx); } } //The following Method is going to Update an existing entity in the table public virtual void Update(T entity) { try { if (entity == null) { throw new ArgumentNullException("Entity"); } if (Context == null || _isDisposed) { Context = new EmployeeDBContext(); } Context.Entry(entity).State = EntityState.Modified; //commented out call to SaveChanges as Context save changes will be called with Unit of work //Context.SaveChanges(); } catch (DbEntityValidationException dbEx) { HandleUnitOfWorkException(dbEx); throw new Exception(_errorMessage, dbEx); } } //The following Method is going to Delete an existing entity from the table public virtual void Delete(T entity) { try { if (entity == null) { throw new ArgumentNullException("Entity"); } if (Context == null || _isDisposed) { Context = new EmployeeDBContext(); } Entities.Remove(entity); //commented out call to SaveChanges as Context save changes will be called with Unit of work //Context.SaveChanges(); } catch (DbEntityValidationException dbEx) { HandleUnitOfWorkException(dbEx); throw new Exception(_errorMessage, dbEx); } } private void HandleUnitOfWorkException(DbEntityValidationException dbEx) { foreach (var validationErrors in dbEx.EntityValidationErrors) { foreach (var validationError in validationErrors.ValidationErrors) { _errorMessage = _errorMessage + $"Property: {validationError.PropertyName} Error: {validationError.ErrorMessage} {Environment.NewLine}"; } } } } }
Now, modify the IEmployeeRepository as shown below
using RepositoryUsingEFinMVC.DAL; using RepositoryUsingEFinMVC.GenericRepository; using System.Collections.Generic; namespace RepositoryUsingEFinMVC.Repository { public interface IEmployeeRepository : IGenericRepository<Employee> { IEnumerable<Employee> GetEmployeesByGender(string Gender); IEnumerable<Employee> GetEmployeesByDepartment(string Dept); } }
Modify the EmployeeRepository as shown below:
Here, we need to define methods specific to the Employee Repository. That means the methods specific to the Employee entity must be defined here. So, modify the EmployeeRepository as follows:
using System.Linq; using System.Collections.Generic; using RepositoryUsingEFinMVC.DAL; using RepositoryUsingEFinMVC.GenericRepository; using RepositoryUsingEFinMVC.UnitOfWork; namespace RepositoryUsingEFinMVC.Repository { public class EmployeeRepository : GenericRepository<Employee>, IEmployeeRepository { //While Creating an Instance of EmployeeRepository, we need to pass the UnitOfWork instance //That UnitOfWork instance contains the Context Object that our EmployeeRepository is going to use public EmployeeRepository(IUnitOfWork<EmployeeDBContext> unitOfWork) : base(unitOfWork) { } //If you don't want to use the Unit Of Work, then use the following Constructor public EmployeeRepository(EmployeeDBContext context) : base(context) { } //The following Method is going to return the Employees by Gender public IEnumerable<Employee> GetEmployeesByGender(string Gender) { return Context.Employees.Where(emp => emp.Gender == Gender).ToList(); } //The following Method is going to return the Employees by Department public IEnumerable<Employee> GetEmployeesByDepartment(string Dept) { return Context.Employees.Where(emp => emp.Dept == Dept).ToList(); } } }
Client Code:
In your application code, you need to create an instance of the Unit of Work and use it to manage transactions and repositories. You begin a transaction, perform database operations through repositories, and then commit the transaction if everything is successful. You can roll back the transaction to maintain data consistency if an error occurs. Next, Modify the Employee Controller as shown below.
using System; using RepositoryUsingEFinMVC.Repository; using System.Web.Mvc; using RepositoryUsingEFinMVC.DAL; using RepositoryUsingEFinMVC.GenericRepository; using RepositoryUsingEFinMVC.UnitOfWork; namespace RepositoryUsingEFinMVC.Controllers { public class EmployeeController : Controller { //While Creating an Instance of UnitOfWork, we need to specify the Actual Context Object private UnitOfWork<EmployeeDBContext> unitOfWork = new UnitOfWork<EmployeeDBContext>(); private GenericRepository<Employee> genericRepository; private IEmployeeRepository employeeRepository; public EmployeeController() { //If you want to use Generic Repository with Unit of work genericRepository = new GenericRepository<Employee>(unitOfWork); //If you want to use a Specific Repository with Unit of work employeeRepository = new EmployeeRepository(unitOfWork); } [HttpGet] public ActionResult Index() { //Using Generic Repository var model = genericRepository.GetAll(); //Using Specific Repository //var model = employeeRepository.GetEmployeesByDepartment(1); return View(model); } [HttpGet] public ActionResult AddEmployee() { return View(); } [HttpPost] public ActionResult AddEmployee(Employee model) { try { //First, Begin the Transaction unitOfWork.CreateTransaction(); if (ModelState.IsValid) { //Do the Database Operation genericRepository.Insert(model); //Call the Save Method to call the Context Class Save Changes Method unitOfWork.Save(); //Do Some Other Tasks with the Database //If everything is working then commit the transaction else rollback the transaction unitOfWork.Commit(); return RedirectToAction("Index", "Employee"); } } catch (Exception ex) { //Log the exception and rollback the transaction unitOfWork.Rollback(); } return View(); } [HttpGet] public ActionResult EditEmployee(int EmployeeId) { Employee model = genericRepository.GetById(EmployeeId); return View(model); } [HttpPost] public ActionResult EditEmployee(Employee model) { if (ModelState.IsValid) { genericRepository.Update(model); unitOfWork.Save(); return RedirectToAction("Index", "Employee"); } else { return View(model); } } [HttpGet] public ActionResult DeleteEmployee(int EmployeeId) { Employee model = genericRepository.GetById(EmployeeId); return View(model); } [HttpPost] public ActionResult Delete(int EmployeeID) { Employee model = genericRepository.GetById(EmployeeID); genericRepository.Delete(model); unitOfWork.Save(); return RedirectToAction("Index", "Employee"); } } }
When to Use Unit of Work in C#?
The Unit of Work (UoW) pattern in C# should be used in scenarios where you must manage transactions and ensure data consistency across multiple operations involving data access. Here are some situations where using the Unit of Work pattern can be beneficial:
- Complex Transactions: When your application has complex business transactions that involve multiple database operations, UoW ensures that these operations are treated as a single unit. This is particularly useful when maintaining atomicity, consistency, isolation, and durability (ACID) properties.
- Batch Operations: If your application requires batch processing where multiple operations need to be executed together, UoW can manage and optimize these operations efficiently.
- Maintaining Object State: UoW is beneficial when working with an object-relational mapping (ORM) framework like Entity Framework. It keeps track of the object state (added, modified, deleted) during the transaction and applies all changes at once when committing the transaction.
- Concurrency Control: In scenarios where multiple processes or users might change the same data concurrently, UoW can help manage concurrency issues, ensuring data integrity and consistency.
- Persistence Ignorance: UoW promotes persistence ignorance in the domain model, allowing the business logic to be agnostic of the persistence layer implementation details.
- Reducing Database Roundtrips: By batching database operations together, UoW can reduce the number of roundtrips to the database, leading to performance improvements, especially in high-latency network environments.
- Change Tracking and Rollback: UoW makes tracking changes across multiple operations easier and rolling them back if something goes wrong, ensuring the system state remains consistent.
- Disconnected Scenarios: In applications where you work with disconnected data scenarios (like web applications), UoW helps you track and save changes made to the data when reconnecting to the database.
When Not to Use Unit of Work in C#?
- Simple CRUD Operations: For simple applications with basic CRUD (Create, Read, Update, Delete) operations, implementing UoW might be overkill and add unnecessary complexity.
- Small Applications or Microservices: In small-scale applications or microservices where transactions are simple and less frequent, the overhead of implementing UoW may not be justified.
- Performance Overhead: In certain high-performance or low-latency scenarios, the overhead of managing UoW might outweigh its benefits.
Considerations of UoW Pattern:
- Complexity: Implementing the UoW and Repository patterns can introduce additional complexity to your codebase, especially in smaller applications. The additional layers and abstractions might be overkill for simple projects.
- Learning Curve: Developers new to these patterns may need time to learn and understand how they work. This can slow down development initially.
- Performance Overhead: In some cases, the additional layers of abstraction introduced by these patterns can lead to a slight performance overhead. However, this impact is usually negligible unless you have extremely high-performance requirements.
- Maintenance: Maintaining UoW and Repository implementations can be challenging if not done correctly. It’s essential to keep the codebase clean and avoid mixing concerns within these patterns.
- Increased Code Volume: UoW and Repository patterns can increase the code you need to write, which may not be justified for simple CRUD operations.
In the next article, I will discuss the Inversion of Control in C# with Examples. In this article, I explain the Unit of Work in C# Repository Pattern using ASP.NET MVC application with Entity Framework step by step with a simple example. I hope you enjoy this article.
Very Simple and Effective
The Interface IDbSet is missing i guess. It cannot be built because of this
Very nice. Simple and easy to understand. Thank you
Great, Thank you , Very Simple and useful
What is public GenericRepository GenericRepository() method in UnitOfWork class for?
it will be return a Repository Instance.
Example my code:
var a2 = unitOfWork.GetRepository().GetAll();
I feel it’s very nice, clean and clear example to understand the concept .
Thank you very much for providing us this site
Excellent article and works like a charm (just replaced IDbSet with DbSet, in reply to comment above). It’s a great foundation to work from and has a nicer syntax to some generic UOW implementations I have seen.
Many Thanks
Thank you very much.
You already help me a lot.
Great Work Bro
You already help me a lot.
This line: var repositoryType = typeof(GenericRepository); should be var repositoryType = typeof(GenericRepository); in order for it to work. This is in the GenericRepository() function in the UnitOfWork class.
Nice implementation!
public GenericRepository(IUnitOfWork unitOfWork) : this(unitOfWork.Context)
{
}
unitOfWork is not found.
DbContextTransaction amd DbEntityValidationException? – cannot see where these are defined.
Is an implementation with .net core and dependency injection also possible?
Please check the below article to understand how to implement this using .NET Core.
https://dotnettutorials.net/lesson/unit-of-work-pattern-in-asp-net-core-mvc-using-ef-core/