paint-brush
What You Need to Know About the Unit in Work in C#by@ssukhpinder
310 reads
310 reads

What You Need to Know About the Unit in Work in C#

by Sukhpinder SinghFebruary 16th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The Unit of Work design pattern is a software design pattern that is widely used in software development. It is a way to group database-related operations into a single transaction or “unit of work” to maintain consistency and integrity in the database. It allows developers to batch multiple operations, reducing the number of database trips and improving the application’s performance.
featured image - What You Need to Know About the Unit in Work in C#
Sukhpinder Singh HackerNoon profile picture

The Unit of Work design pattern is a software design pattern that is widely used in software development. It is a way to group database-related operations into a single transaction or “unit of work” to maintain consistency and integrity in the database.


This article will discuss the Unit of Work pattern, its benefits, and how to implement it in C#.

Prerequisites

  • Basic knowledge of OOPS concepts.


  • Any programming language knowledge.

Learning Objectives

  • How to code using a Unit of Work design pattern

Getting Started

The Unit of Work pattern is a way to encapsulate a set of database-related operations into a single transaction. This is done to ensure that all the functions are committed or rolled back together, ensuring consistency in the database.


It also allows developers to batch multiple operations, reducing the number of database trips and improving the application’s performance.

Benefits of the Unit of Work Pattern

There are several benefits of using the Unit of Work pattern, including:


  1. Consistency: The Unit of Work pattern ensures that all related operations are committed or rolled back together, ensuring consistency in the database.


  2. Performance: The Unit of Work pattern reduces the number of database trips, improving the application’s performance.


  3. Encapsulation: The Unit of Work pattern encapsulates database-related operations, making it easier to manage database transactions.


  4. Abstraction: The Unit of Work pattern abstracts the database layer, making it easier to change the database implementation without affecting the application layer.

How to Implement the Unit of Work Pattern in C#

We will use the Repository pattern to implement the Unit of Work pattern in C#. The Repository pattern is a way to encapsulate the data access layer and provides a layer of abstraction between the application and the database.

Step 1: Define the Unit of Work Interface

The first step is to define the Unit of Work interface, which will define the contract for the database operations.


public interface IUnitOfWork
{
    void Commit();
    void Rollback();
}

Step 2: Implement the Unit of Work

The next step is to implement the Unit of Work interface. In the implementation, we will create a new instance of the DbContext for each unit of work.


public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _dbContext;
 
    public UnitOfWork(DbContext dbContext)
    {
        _dbContext = dbContext;
    }
 
    public void Commit()
    {
        _dbContext.SaveChanges();
    }
 
    public void Rollback()
    {
        // Implement rollback logic
    }
}

Step 3: Define the Repository Interface

The next step is to define the Repository interface which will determine the contract for the data access operations.


public interface IRepository<T> where T : class
{
    void Add(T entity);
    void Remove(T entity);
    void Update(T entity);
}

Step 4: Implement the Repository

The next step is to implement the Repository interface. We will use the Unit of Work to manage the database transactions in the implementation.


public class Repository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> _dbSet;
    private readonly IUnitOfWork _unitOfWork;
 
    public Repository(DbSet<T> dbSet, IUnitOfWork unitOfWork)
    {
        _dbSet = dbSet;
        _unitOfWork = unitOfWork;
    }
 
    public void Add(T entity)
    {
        _dbSet.Add(entity);
    }
 
    public void Remove(T entity)
    {
        _dbSet.Remove(entity);
    }
 
    public void Update(T entity)
    {
        _dbSet.Update(entity);
    }
 
    public void SaveChanges()
    {
        _unitOfWork.Commit();
    }
}

Step 5: Use the Repository and Unit of Work in the Application

The final step is to use the Repository and Unit of Work in the application.

Use Case

In the banking application, we have to implement various database operations, such as adding a new customer, transferring money from one account to another, and retrieving account details. We will use the Unit of Work pattern to group these operations into a single transaction.

Step 1: Define the Unit of Work Interface

We will define the Unit of Work interface, which will determine the contract for the database operations.


public interface IUnitOfWork
{
    void Commit();
    void Rollback();
    IRepository<Customer> Customers { get; }
    IRepository<Account> Accounts { get; }
    IRepository<Transaction> Transactions { get; }
}

Step 2: Implement the Unit of Work

We will implement the Unit of Work interface. In the implementation, we will create a new instance of the DbContext for each unit of work.


public class UnitOfWork : IUnitOfWork
{
    private readonly BankingDbContext _dbContext;
    private IRepository<Customer> _customers;
    private IRepository<Account> _accounts;
    private IRepository<Transaction> _transactions;

    public UnitOfWork(BankingDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    public IRepository<Customer> Customers
    {
        get
        {
            if (_customers == null)
            {
                _customers = new Repository<Customer>(_dbContext.Customers, this);
            }
            return _customers;
        }
    }
    public IRepository<Account> Accounts
    {
        get
        {
            if (_accounts == null)
            {
                _accounts = new Repository<Account>(_dbContext.Accounts, this);
            }
            return _accounts;
        }
    }

    public IRepository<Transaction> Transactions
    {
        get
        {
            if (_transactions == null)
            {
                _transactions = new Repository<Transaction>(_dbContext.Transactions, this);
            }
            return _transactions;
        }
    }

    public void Commit()
    {
        _dbContext.SaveChanges();
    }

    public void Rollback()
    {
        // Implement rollback logic
    }
}

Step 3: Define the Repository Interface

We will define the Repository interface and determine the contract for the data access operations.


public interface IRepository<T> where T : class
{
    void Add(T entity);
    void Remove(T entity);
    void Update(T entity);
    IEnumerable<T> GetAll();
    T GetById(int id);
}

Step 4: Implement the Repository

We will implement the Repository interface. We will use the Unit of Work to manage the database transactions in the implementation.


public class Repository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> _dbSet;
    private readonly IUnitOfWork _unitOfWork;
    
    public Repository(DbSet<T> dbSet, IUnitOfWork unitOfWork)
    {
        _dbSet = dbSet;
        _unitOfWork = unitOfWork;
    }
    public void Add(T entity)
    {
        _dbSet.Add(entity);
    }
    public void Remove(T entity)
    {
        _dbSet.Remove(entity);
    }
    public void Update(T entity)
    {
        _dbSet.Update(entity);
    }
    public IEnumerable<T> GetAll()
    {
        return _dbSet.ToList();
    }
    public T GetById(int id)
    {
        return _dbSet.Find(id);
    }
    public void SaveChanges()
    {
        _unitOfWork.Commit();
    }
}

Step 5: Use the Repository and Unit of Work in the Application

We will use the application's Repository and Unit of Work to perform various database operations.


For example, to add a new customer, we can create a new instance of the Customer class, add it to the Customers repository, and then call the SaveChanges method of the repository to commit the changes to the database. Here’s an example code snippet:


var unitOfWork = new UnitOfWork(new BankingDbContext());
var customer = new Customer
{
    FirstName = "John",
    LastName = "Doe",
    Email = "[email protected]"
};
unitOfWork.Customers.Add(customer);
unitOfWork.Customers.SaveChanges();

Similarly, we can use the Unit of Work and Repository to perform other database operations, such as transferring money from one account to another, retrieving account details, etc.


Using the Unit of Work pattern, we can group these operations into a single transaction, ensuring that all operations succeed or fail together.

Conclusion

This article discusses the Unit of Work pattern and how it can be used in C# applications to manage database transactions. We have seen how the design helps to group database operations into a single transaction and ensures that all the operations succeed or fail together.


Using the Unit of Work pattern, we can improve the performance and scalability of our applications and make them more robust and reliable.


C# Publication, LinkedIn, Instagram, Twitter, Dev.to, BuyMeACoffee


Also published here