← All topics
🧩 Design Patterns

Repository Pattern

Abstracting persistence behind a collection-like interface.

A Repository presents domain code with a collection-like interface (GetById, Add, Remove) over persistence, hiding EF Core/SQL specifics from the rest of the app. The honest, senior-level nuance to mention: with EF Core, DbContext already implements Unit of Work and its DbSet already looks a lot like a repository — a hand-rolled generic IRepository on top of EF Core can be redundant ceremony ("just wraps DbSet"). The pattern earns its keep when you need to (a) genuinely swap persistence technology, (b) centralize non-trivial query logic (specifications), or (c) keep domain code testable without spinning up a real/in-memory DbContext in every test.

public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id);
    Task AddAsync(Order order);
    Task<IReadOnlyList<Order>> GetByCustomerAsync(string customerId);
}

public class EfOrderRepository(AppDbContext db) : IOrderRepository
{
    public Task<Order?> GetByIdAsync(Guid id) => db.Orders.FindAsync(id).AsTask();
    public Task AddAsync(Order order) { db.Orders.Add(order); return Task.CompletedTask; }
    public Task<IReadOnlyList<Order>> GetByCustomerAsync(string customerId) =>
        db.Orders.Where(o => o.CustomerId == customerId).ToListAsync()
          .ContinueWith(t => (IReadOnlyList<Order>)t.Result);
}

Flashcards (2)

What's the honest critique of adding a generic IRepository<T> on top of EF Core's DbSet<T>?
tap to reveal answer
DbSet<T> + DbContext already behave like a repository + unit of work, so a thin generic wrapper can be redundant ceremony that just delegates — the pattern earns its place for genuine persistence-swap needs, centralizing complex queries, or decoupling domain tests from EF Core.
What three concrete reasons justify a Repository abstraction even when using EF Core?
tap to reveal answer
1) You genuinely might swap the persistence technology. 2) You want to centralize non-trivial/reused query logic in one place. 3) You want domain/service-layer unit tests that don't need a real or in-memory DbContext.