Learn and Teach Coding
.NET

.NET Repository Pattern

Implement the repository pattern with Entity Framework Core.

.NETIntermediateentity-frameworkrepositorypatternsasp.net

Source code

public interface IRepository<T> where T : class
{
    Task<T?> GetByIdAsync(int id);
    Task<IReadOnlyList<T>> ListAsync();
    Task AddAsync(T entity);
    void Remove(T entity);
    Task<int> SaveChangesAsync();
}

Walkthrough

The repository pattern puts a thin abstraction over data access, so the rest of your app depends on an interface rather than on Entity Framework directly. That keeps your business logic testable and your persistence swappable. Both files are on the left.

1. The contract

A generic IRepository<T> describes what every repository can do, with no EF Core types leaking out. Your services depend on this — not on DbContext.

public interface IRepository<T> where T : class
{
    Task<T?> GetByIdAsync(int id);
    Task<IReadOnlyList<T>> ListAsync();
    Task AddAsync(T entity);
    void Remove(T entity);
    Task<int> SaveChangesAsync();
}

2. A generic base implementation

Repository<T> resolves the right DbSet<T> from the context, so one class serves every entity — no per-entity boilerplate.

public Repository(AppDbContext db)
{
    _db = db;
    _set = db.Set<T>();
}

3. Reads

FindAsync hits the change-tracker/identity-map first. For list queries we add AsNoTracking() because read-only results don't need change tracking — it's faster and lighter.

public async Task<IReadOnlyList<T>> ListAsync() =>
    await _set.AsNoTracking().ToListAsync();

Use AsNoTracking() for any query whose results you won't modify and save — it skips the snapshot EF would otherwise keep for change detection.

4. Writes and the unit of work

AddAsync/Remove stage changes; nothing touches the database until SaveChangesAsync. Exposing save separately lets a caller batch several operations into one transaction.

public async Task AddAsync(T entity) => await _set.AddAsync(entity);
public void Remove(T entity) => _set.Remove(entity);
public Task<int> SaveChangesAsync() => _db.SaveChangesAsync();

Entity-specific repositories can subclass this base to add queries that only make sense for one type. The full files are in the panel.