DEV Community

Cover image for Unit Of Work Pattern In C# For Clean Architecture: What You Need To Know
Dev Leader
Dev Leader

Posted on • Originally published at devleader.ca

Unit Of Work Pattern In C# For Clean Architecture: What You Need To Know

The post Unit of Work Pattern in C# for Clean Architecture: What You Need To Know appeared first on Dev Leader.

Let’s explore the Unit of Work Pattern in C# for Clean Architecture! Clean Architecture is a popular paradigm in software engineering, and understanding the Unit of Work Pattern is a helpful pattern we can leverage when developing tidy, maintainable, and understandable code. We can put these two concepts together to help us build solid applications.

The Unit of Work Pattern is a design pattern that provides an efficient way of handling database transactions. It is a part of the n-tier architecture that separates the business logic from the data access logic. This pattern helps developers focus on the code’s functionality, abstract the database layer, and improve the overall code quality.

Clean Architecture is designed to achieve maintainable, flexible, and testable code that can be easily changed or adapted, making the code more resilient to change. It fosters the separation of concerns within a software project, which improves the code quality, performance, and maintainability.

In the following sections, I will explain the Unit of Work Pattern, Clean Architecture, and how they work together. We’ll see some practical tips and code examples that will help you enhance your skills and knowledge of both of these topics!


Understanding the Unit of Work Pattern in CSharp

The Unit of Work pattern is a design pattern used in software engineering to manage database transactions. It is responsible for coordinating multiple repositories to ensure consistency and atomicity of database transactions. The Unit of Work pattern provides several benefits, including improved performance, reduced coupling between repositories and the database, and easier testability.

To implement the Unit of Work pattern in C#, follow these steps:

  1. Define an interface for the Unit of Work class that implements a variety of methods, such as Commit() and Rollback().

  2. Create a concrete implementation of the Unit of Work interface that manages the lifetime of the database context object.

  3. Define an interface for the repository class that implements methods like Add(), Update(), and Delete().

  4. Create a concrete implementation of the repository interface that utilizes the Unit of Work for transaction management.

Below is an example of how to implement the Unit of Work pattern in C#:

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

public class UnitOfWork : IUnitOfWork
{
    private readonly IDbContext _dbContext;

    public UnitOfWork(IDbContext dbContext)
    {
        _dbContext = dbContext;
    }

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

    public void Rollback()
    {
        // Rollback logic here
    }
}

public interface IRepository<T>
{
    void Add(T entity);
    void Update(T entity);
    void Delete(T entity);
}

public class Repository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> _dbSet;

    public Repository(IDbContext dbContext)
    {
        _dbSet = dbContext.Set<T>();
    }

    public void Add(T entity)
    {
        _dbSet.Add(entity);
    }

    public void Update(T entity)
    {
        _dbSet.Attach(entity);
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }
}
Enter fullscreen mode Exit fullscreen mode

By implementing the Unit of Work pattern in C#, you can easily manage database transactions using a single interface, reduce coupling between repositories and the database, and simplify testing of your data access layer.

Getting Started: Clean Architecture in .NET

It's time to level up! Check out this course on Dometrain!


Clean Architecture in CSharp

Clean architecture is a software design approach that prioritizes the separation of concerns and the independence of frameworks and tools. This approach is centered around the idea of creating software systems that are easy to read, maintain, and test.

One of the core benefits of Clean Architecture is that it promotes an organized, maintainable, and scalable software structure that is easy to maintain over the lifetime of a project. Another benefit is that it promotes a focus on business logic over infrastructure and implementation details, which allows developers to design and build software systems that are more adaptable to change.

When used in conjunction with the Unit of Work Pattern, Clean Architecture can become even more powerful, as it provides a clear separation of concerns between business logic and data access. By abstracting away the database implementation details into the Unit of Work Pattern, applications can become more flexible and easier to test.

Some of the key benefits of using Clean Architecture are:

  • Increased flexibility

  • Easier testing

  • Improved maintainability

  • Separation of concerns

  • Reduced coupling

To see the benefits of Clean Architecture in action, consider the following code example:

// this is an example of a (potentially) poorly organized, tightly-coupled code structure
// - directly accessing the DB context to fetch
// - directly accessing the DB context to write
public class MyClass
{
    private readonly IDbContext _dbContext;

    public MyClass(IDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void DoSomething(int id)
    {
        var entity = _dbContext.Set<Entity>().FirstOrDefault(e => e.Id == id);

        // ...

        _dbContext.SaveChanges();
    }
}

// this is an example of a clean code structure using Clean Architecture
// - using a repository interface to read
// - using a unit of work pattern to write
public class MyClass
{
    private readonly IRepository<Entity> _entityRepository;
    private readonly IUnitOfWork _unitOfWork;

    public MyClass(IRepository<Entity> entityRepository, IUnitOfWork unitOfWork)
    {
        _entityRepository = entityRepository;
        _unitOfWork = unitOfWork;
    }

    public void DoSomething(int id)
    {
        var entity = _entityRepository.GetById(id);

        // ...

        _unitOfWork.Commit();
    }
}
Enter fullscreen mode Exit fullscreen mode

By adopting Clean Architecture in conjunction with the Unit of Work Pattern, developers can create more scalable, maintainable, and testable software systems that are easier to work with over the lifetime of a project. By focusing on independent modules with well-defined interfaces, developers can achieve increased flexibility and reduced coupling, which leads to systems that are easier to evolve and extend over time.


Implementing the Unit of Work Pattern in Clean Architecture

To implement the Unit of Work pattern in Clean Architecture, use the following steps:

  1. Define an interface for the Unit of Work class that includes methods to manage database transactions, such as Commit() and Rollback().

  2. Create a concrete implementation of the Unit of Work interface that manages the lifetime of the database context object. This should be done in the outer-most layer of the application, which may be a database access layer or a service layer.

  3. Define an interface for each repository class that includes methods like Add(), Update(), and Delete(). These methods should use the Unit of Work to manage database transactions.

  4. Create concrete implementations of each repository interface using a specific database implementation (like Entity Framework).

Code Example of the Unit of Work Pattern in Clean Architecture

Here is an example code structure for implementing the Unit of Work Pattern in Clean Architecture:

// Application Layer
public interface IUnitOfWork
{
    void Commit();
    void Rollback();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly IDbContext _dbContext;

    public UnitOfWork(IDbContext dbContext)
    {
        _dbContext = dbContext;
    }

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

    public void Rollback()
    {
        // Rollback logic here
    }
}

// Domain Layer
public interface IEntityRepository<T>
{
    void Add(T entity);
    void Update(T entity);
    void Delete(T entity);
}

public class EntityRepository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> _dbSet;

    public EntityRepository(IDbContext dbContext)
    {
        _dbSet = dbContext.Set<T>();
    }

    public void Add(T entity)
    {
        _dbSet.Add(entity);
    }

    public void Update(T entity)
    {
        _dbSet.Attach(entity);
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }
}

// Infrastructure Layer (Entity Framework-specific implementation)
public class EntityFrameworkDbContext : IDbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("your-connection-string-here");
    }

    public DbSet<Entity> Entities { get; set; }
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly EntityFrameworkDbContext _dbContext;

    public EntityFrameworkUnitOfWork(EntityFrameworkDbContext dbContext)
    {
        _dbContext = dbContext;
    }

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

    public void Rollback()
    {
        // Rollback logic here
    }
}

public class EntityFrameworkEntityRepository<T> : IEntityRepository<T> where T : class
{
    private readonly EntityFrameworkDbContext _dbContext;
    private readonly DbSet<T> _dbSet;

    public EntityFrameworkEntityRepository(EntityFrameworkDbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = dbContext.Set<T>();
    }

    public void Add(T entity)
    {
        _dbSet.Add(entity);
    }

    public void Update(T entity)
    {
        _dbSet.Attach(entity);
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity);
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of Implementing the Unit of Work Pattern in Clean Architecture

Implementing the Unit of Work pattern in Clean Architecture provides many benefits, including improved code maintainability, scalability, and testability. The following are the key benefits of this approach:

  1. Reduced Coupling: By abstracting away the lower-level database code into the Unit of Work pattern, this implementation reduces coupling between the repository layer and the database. This makes it easier to switch out database implementations or make database schema changes without affecting higher-level business logic in the application’s layers.

  2. Improved Maintainability: The Unit of Work pattern makes it easier to maintain the application codebase since developers don’t need to worry about managing multiple database connections or transactions. This makes the code cleaner and easier to understand. Additionally, using the Clean Architecture approach means that different layers of the application are organized according to their concerns, which further improves maintainability.

  3. Simplified Testing: With the Unit of Work pattern and Clean Architecture approach, developers can more easily test the application. They can create mock objects for the repository classes and Unit of Work and test business logic without creating integration tests with a database. This makes testing faster, less brittle, and saves valuable development time.

By implementing the Unit of Work pattern in Clean Architecture, developers can build applications that are flexible, maintainable, and scalable. The separation of concerns enabled by the clean architecture approach makes it easier to maintain or change code over time with minimal impact on other application layers. Finally, this approach provides developers with a simplified testing process that helps them write better code faster.

Getting Started: Clean Architecture in .NET

It's time to level up! Check out this course on Dometrain!


Common Mistakes & Suggestions for the Unit of Work Pattern

Despite the many benefits of the Unit of Work pattern in C#, there are several common mistakes that developers make when implementing this design pattern. Some of these mistakes can lead to bugs, poor performance, or other issues that affect the overall quality of the software.

Common Mistakes When Implementing the Unit of Work Pattern

The following are some common mistakes made when implementing the Unit of Work pattern:

  • Overcomplicating the Unit of Work: The Unit of Work pattern is intended to simplify data access by providing a single interface for database transactions. However, some developers overcomplicate this interface by including unnecessary methods or business logic. This results in bloated and difficult to understand code

  • Not properly managing transactions: One of the main benefits of the Unit of Work pattern is the ability to manage transactions using the Commit() and Rollback() methods. However, some developers fail to manage these transactions properly, which can result in partial updates or corrupted data

  • Failing to properly abstract the database layer: If the Unit of Work pattern is not properly abstracted from the database layer, developers may end up with tight coupling between their application and the database. This can make it difficult to make changes to the database schema or switch to a new database technology, and may result in poor performance.

Suggestions When Implementing the Unit of Work Pattern

To avoid these common mistakes, it’s important to follow best practices when implementing the Unit of Work pattern. Some tips for avoiding these mistakes include:

  • Keeping the Unit of Work simple: The Unit of Work interface should only include the methods necessary to manage transactions. Additional business logic should be kept in the repository layer or other parts of the application.

  • Implementing the Unit of Work correctly: Developers should ensure that transactions are properly managed using the Commit() and Rollback() methods. If possible, developers should use a transaction scope to ensure that transactions are atomic and consistent.

  • Abstracting the database layer: The Unit of Work pattern should be properly abstracted from the database layer to allow for flexibility and maintainability. This can be accomplished by using an ORM like Entity Framework that provides a layer of abstraction between the application and the database.

By following these tips and avoiding common mistakes, developers can implement the Unit of Work pattern in a way that maximizes its benefits and improves the overall quality of their software.


Wrapping Up the Unit of Work Pattern in CSharp for Clean Architecture

The Unit of Work Pattern in C# for Clean Architecture can be very helpful for software engineers to follow. It allows for better separation of concerns, simpler code maintenance, improved testability, and easier scalability. By utilizing Clean Architecture with the Unit of Work pattern, you can take full advantage of the many benefits that both of these offer.

By using the tips and techniques outlined in this article, you can take your software engineering skills to the next level and create more robust and scalable applications. Fall back to the code examples to see how the pattern can work in its basic form, and see if it makes sense for you while implementing clean architecture.

Try implementing the Unit of Work pattern with Clean Architecture in your project! Doing so can contribute to more efficient development processes and more successful software outcomes.


Want More Dev Leader Content?

  • Follow along on this platform if you haven’t already!
  • Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos: SUBSCRIBE FOR FREE
  • Looking for courses? Check out my offerings: VIEW COURSES
  • E-Books & other resources: VIEW RESOURCES
  • Watch hundreds of full-length videos on my YouTube channel: VISIT CHANNEL
  • Visit my website for hundreds of articles on various software engineering topics (including code snippets): VISIT WEBSITE
  • Check out the repository with many code examples from my articles and videos on GitHub: VIEW REPOSITORY

Top comments (2)

Collapse
 
jangelodev profile image
João Angelo

Hi Dev Leader,
Your tips are very useful
Thanks for sharing

Collapse
 
devleader profile image
Dev Leader

Thanks for the feedback!