Repository Pattern
The Repository Pattern is a design pattern that acts as a layer between your application's business logic and the data source.[1] Instead of your services or controllers talking directly to the database, they talk to a repository — an object that looks like a simple in-memory collection but handles all the database work behind the scenes. The rest of the application does not know or care whether data comes from SQL, MongoDB, or a file.
The pattern is about hiding data access details. Your business logic should not be full of SQL queries or ORM calls. The repository takes that responsibility away, leaving the rest of the code clean and focused on behaviour.
Example in C#
// The interface — what the repository can do
public interface IUserRepository
{
User? GetById(int id);
IEnumerable<User> GetAll();
void Add(User user);
void Delete(int id);
}
// The concrete implementation — uses Entity Framework
public class UserRepository : IUserRepository
{
private readonly AppDbContext _context;
public UserRepository(AppDbContext context)
{
_context = context;
}
public User? GetById(int id) => _context.Users.Find(id);
public IEnumerable<User> GetAll() => _context.Users.ToList();
public void Add(User user) => _context.Users.Add(user);
public void Delete(int id)
{
var user = _context.Users.Find(id);
if (user != null) _context.Users.Remove(user);
}
}
// Usage in a service — no EF, no SQL, just the interface
public class UserService
{
private readonly IUserRepository _repo;
public UserService(IUserRepository repo)
{
_repo = repo;
}
public User? GetUser(int id) => _repo.GetById(id);
}
The service only knows about IUserRepository. You can swap the real implementation for a fake one in tests without touching the service at all.
Why it matters
- Testability: inject a fake repository in unit tests — no real database needed.
- Separation of concerns: data access logic lives in one place, not scattered across services.
- Flexibility: switching from Entity Framework to another ORM only requires changing the repository, not the business logic.
Generic Repository
A common variation is the Generic Repository, which uses a type parameter to avoid repeating the same CRUD methods for every entity:
public interface IRepository<T>
{
T? GetById(int id);
IEnumerable<T> GetAll();
void Add(T entity);
void Delete(int id);
}
This works well for simple CRUD operations, but for queries specific to one entity (like "get all users who signed up this month"), a typed repository like IUserRepository is still clearer.