Section 3 of 5
Unit of Work
🎯 What You'll Learn
- What is Unit of Work
- Creating UnitOfWork interface
- Implementing UnitOfWork
- Transaction management
- Using with repositories
What is Unit of Work?
The Unit of Work pattern maintains a list of changes and coordinates writing them to the database in a single transaction.
Why Use Unit of Work?
- Transaction management: Single SaveChanges() call
- Consistency: All changes succeed or fail together
- Performance: Batch database operations
- Coordination: Manage multiple repositories
Unit of Work Interface
IUnitOfWork.cs
C#
public interface IUnitOfWork : IDisposable
{
IProductRepository Products { get; }
IOrderRepository Orders { get; }
ICustomerRepository Customers { get; }
Task<int> SaveChangesAsync();
}
Unit of Work Implementation
UnitOfWork.cs
C#
public class UnitOfWork : IUnitOfWork
{
private readonly InvenTrackDbContext _context;
private IProductRepository _products;
private IOrderRepository _orders;
private ICustomerRepository _customers;
public UnitOfWork(InvenTrackDbContext context)
{
_context = context;
}
public IProductRepository Products =>
_products ??= new ProductRepository(_context);
public IOrderRepository Orders =>
_orders ??= new OrderRepository(_context);
public ICustomerRepository Customers =>
_customers ??= new CustomerRepository(_context);
public async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
}
Register Unit of Work
Program.cs
C#
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
Using Unit of Work
OrdersController.cs
C#
public class OrdersController : Controller
{
private readonly IUnitOfWork _unitOfWork;
public OrdersController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
[HttpPost]
public async Task<IActionResult> Create(Order order)
{
// Add order
await _unitOfWork.Orders.AddAsync(order);
// Update product stock
foreach (var item in order.Items)
{
var product = await _unitOfWork.Products.GetByIdAsync(item.ProductId);
product.Quantity -= item.Quantity;
await _unitOfWork.Products.UpdateAsync(product);
}
// Save all changes in one transaction
await _unitOfWork.SaveChangesAsync();
return RedirectToAction("Index");
}
}
Modified Repository (No SaveChanges)
ProductRepository.cs
C#
public class ProductRepository : IProductRepository
{
private readonly InvenTrackDbContext _context;
public async Task AddAsync(Product product)
{
await _context.Products.AddAsync(product);
// No SaveChangesAsync() here!
}
public Task UpdateAsync(Product product)
{
_context.Products.Update(product);
// No SaveChangesAsync() here!
return Task.CompletedTask;
}
}
Key Takeaways
- Unit of Work: Coordinates multiple repositories
- Single transaction: One SaveChangesAsync() call
- Consistency: All or nothing
- Repositories: Don't call SaveChanges()
- UnitOfWork: Calls SaveChanges() once
- Pattern combination: Works with Repository Pattern