DbContext is already a Unit of Work and DbSet is already a repository.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // ====================================================================== // Q: Repository and Unit of Work on top of EF Core: yes or no? // // DbContext is already a Unit of Work and DbSet is already a repository. A // generic IRepository<T> that exposes IQueryable adds nothing. A repository per // aggregate root (DDD) is worthwhile, though: it always loads the whole // aggregate and keeps the domain independent of EF. // For reads (lists, reports) you don't need a repository: DbContext with // AsNoTracking() and projection to a DTO (Q1). For reusable query criteria there // is the Specification pattern (e.g. Ardalis.Specification). // ====================================================================== public interface IOrderRepository // in Domain / Application { Task<Order?> GetAsync(int id, CancellationToken ct); void Add(Order order); } public interface IUnitOfWork { Task<int> SaveChangesAsync(CancellationToken ct = default); } public class EfOrderRepository(AppDbContext db) : IOrderRepository // in Infrastructure { public Task<Order?> GetAsync(int id, CancellationToken ct) => db.Orders.Include(o => o.Lines).FirstOrDefaultAsync(o => o.Id == id, ct); public void Add(Order order) => db.Orders.Add(order); } public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options), IUnitOfWork { public DbSet<Order> Orders => Set<Order>(); } builder.Services.AddScoped<IOrderRepository, EfOrderRepository>(); builder.Services.AddScoped<IUnitOfWork>(sp => sp.GetRequiredService<AppDbContext>()); // same scoped instance |