Introduction
As software applications grow in complexity, maintaining clean and manageable code becomes increasingly important. The Unit of Work pattern is a widely used design pattern that helps manage database transactions and promotes a clean separation of concerns. In this article, we will explore the Unit of Work pattern, its benefits, and how to implement it in C# for modern applications.
What is the Unit of Work Pattern?
The Unit of Work pattern is a design pattern that helps manage the transactional work of multiple operations. It acts as a coordinator for a set of actions that need to be performed as a single unit. This ensures that all operations within the unit either complete successfully or roll back together, maintaining the integrity of the database.
Benefits of the Unit of Work Pattern
- Transaction Management: Ensures that a group of operations are treated as a single transaction.
- Minimized Database Access: Reduces the number of database calls by batching multiple operations.
- Consistency: Maintains data integrity by ensuring that all operations succeed or fail as a unit.
- Separation of Concerns: Decouples business logic from data access logic.
Implementing the Unit of Work Pattern in C#
To demonstrate the implementation of the Unit of Work pattern in C#, we'll use Entity Framework Core, a popular ORM for .NET applications. We'll create a sample application that manages a list of books and authors.
Step 1. Setting Up the Project
- Create a new project
dotnet new console -n UnitOfWorkExample cd UnitOfWorkExample dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer - Define the models
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public ICollection<Book> Books { get; set; } } public class Book { public int BookId { get; set; } public string Title { get; set; } public int AuthorId { get; set; } public Author Author { get; set; } } - Create the DbContext
public class LibraryContext : DbContext { public DbSet<Author> Authors { get; set; } public DbSet<Book> Books { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("YourConnectionStringHere"); } }
Step 2. Creating Repositories
Repositories help abstract the data access logic. We will create generic repositories for basic CRUD operations.
- Generic Repository Interface
public interface IRepository<T> where T : class { Task<IEnumerable<T>> GetAllAsync(); Task<T> GetByIdAsync(int id); Task AddAsync(T entity); void Update(T entity); void Delete(T entity); } - Generic Repository Implementation
public class Repository<T> : IRepository<T> where T : class { private readonly LibraryContext _context; private readonly DbSet<T> _dbSet; public Repository(LibraryContext context) { _context = context; _dbSet = _context.Set<T>(); } public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync(); public async Task<T> GetByIdAsync(int id) => await _dbSet.FindAsync(id); public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity); public void Update(T entity) => _dbSet.Update(entity); public void Delete(T entity) => _dbSet.Remove(entity); }
Step 3. Implementing the Unit of Work
- Unit of Work Interface
public interface IUnitOfWork : IDisposable { IRepository<Author> Authors { get; } IRepository<Book> Books { get; } Task<int> CompleteAsync(); } - Unit of Work Implementation
public class UnitOfWork : IUnitOfWork { private readonly LibraryContext _context; private IRepository<Author> _authors; private IRepository<Book> _books; public UnitOfWork(LibraryContext context) { _context = context; } public IRepository<Author> Authors => _authors ??= new Repository<Author>(_context); public IRepository<Book> Books => _books ??= new Repository<Book>(_context); public async Task<int> CompleteAsync() => await _context.SaveChangesAsync(); public void Dispose() => _context. Dispose(); }
Step 4. Using the Unit of Work
Finally, let's create a simple service that uses the Unit of Work to manage transactions.
- Book Service
public class BookService { private readonly IUnitOfWork _unitOfWork; public BookService(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public async Task AddBookAsync(string title, int authorId) { var book = new Book { Title = title, AuthorId = authorId }; await _unitOfWork.Books.AddAsync(book); await _unitOfWork.CompleteAsync(); } public async Task<IEnumerable<Book>> GetBooksAsync() => await _unitOfWork.Books.GetAllAsync(); } - Main Method
public class Program { public static async Task Main(string[] args) { var options = new DbContextOptionsBuilder<LibraryContext>() .UseSqlServer("YourConnectionStringHere") .Options; using var context = new LibraryContext(options); var unitOfWork = new UnitOfWork(context); var bookService = new BookService(unitOfWork); await bookService.AddBookAsync("C# in Depth", 1); var books = await bookService.GetBooksAsync(); foreach (var book in books) { Console.WriteLine(book.Title); } } }
Conclusion
The Unit of Work pattern is a valuable tool for managing database transactions cleanly and efficiently. Coordinating multiple operations within a single transaction ensures data integrity and reduces database access. Implementing this pattern in C# with Entity Framework Core helps in creating maintainable and scalable applications.

Join the conversation! Your thoughts help the community grow.