This content originally appeared on DEV Community and was authored by Saumya-Ranjan-Mishra
Unit Of Work.. What is it ?
The Unit of Work pattern is a design pattern used to group multiple operations (usually repository calls) into a single transaction. It ensures that either all operations succeed or none are applied, maintaining data consistency.
It usually works alongside the Repository pattern, which abstracts data access logic.
As I said it can work with repository patten, I will keep the code fairly simple rather than over-complicating it with generic repository.
Setting up the repositories first…
entities
// Models/Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
// Models/Order.cs
public class Order
{
public int Id { get; set; }
public int ProductId { get; set; }
public DateTime OrderDate { get; set; }
}
// Models/Log.cs
public class Log
{
public int Id { get; set; }
public string Message { get; set; }
public DateTime Timestamp { get; set; }
}
set the DbContext
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Log> Logs { get; set; }
}
Define the repositories
public interface IProductRepository
{
Task<Product> GetByIdAsync(int id);
Task AddAsync(Product product);
void Update(Product product);
void Delete(Product product);
}
public interface IOrderRepository
{
Task AddAsync(Order order);
}
public interface ILogRepository
{
Task AddAsync(Log log);
}
Implement the repositories
// Product repositories
public class ProductRepository : IProductRepository
{
private readonly AppDbContext _context;
public ProductRepository(AppDbContext context)
{
_context = context;
}
public async Task<Product> GetByIdAsync(int id)
{
return await _context.Products.FindAsync(id);
}
public async Task AddAsync(Product product)
{
await _context.Products.AddAsync(product);
}
public void Update(Product product)
{
_context.Products.Update(product);
}
public void Delete(Product product)
{
_context.Products.Remove(product);
}
}
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public OrderRepository(AppDbContext context)
{
_context = context;
}
public async Task AddAsync(Order order)
{
await _context.Orders.AddAsync(order);
}
}
public class LogRepository : ILogRepository
{
private readonly AppDbContext _context;
public LogRepository(AppDbContext context)
{
_context = context;
}
public async Task AddAsync(Log log)
{
await _context.Logs.AddAsync(log);
}
}
Unit of Work Interface & Implementation
public interface IUnitOfWork : IDisposable
{
IProductRepository Products { get; }
IOrderRepository Orders { get; }
ILogRepository Logs { get; }
Task<int> CompleteAsync();
}
Implementation of IUnitOfWork
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
public IProductRepository Products { get; }
public IOrderRepository Orders { get; }
public ILogRepository Logs { get; }
public UnitOfWork(
AppDbContext context,
IProductRepository productRepository,
IOrderRepository orderRepository,
ILogRepository logRepository)
{
_context = context;
Products = productRepository;
Orders = orderRepository;
Logs = logRepository;
}
public async Task<int> CompleteAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
}
Inject the dependencies
Make sure you register the dependencies as AddScoped, so that all the repositories will share the same instance of DbContext in a particular http call.
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<ILogRepository, LogRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
Operations in controller
[ApiController]
[Route("api/[controller]")]
public class CheckoutController : ControllerBase
{
private readonly IUnitOfWork _unitOfWork;
public CheckoutController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
[HttpPost]
public async Task<IActionResult> Checkout([FromBody] ProductOrderDto dto)
{
var product = new Product { Name = dto.ProductName, Price = dto.Price };
var order = new Order { ProductId = product.Id, OrderDate = DateTime.UtcNow };
var log = new Log { Message = "New order placed", Timestamp = DateTime.UtcNow };
await _unitOfWork.Products.AddAsync(product);
await _unitOfWork.Orders.AddAsync(order);
await _unitOfWork.Logs.AddAsync(log);
// Single transaction
await _unitOfWork.CompleteAsync();
return Ok("Order placed successfully.");
}
}
Congratulation! You have successfully implemented unit of work there by saving your DB from a possible inconsistent state.
Feel free to ask any question. Or ping in linkedin
This content originally appeared on DEV Community and was authored by Saumya-Ranjan-Mishra