Abstract data access logic behind a clean interface for testability and flexibility.
Code Snippet
// IRepository.cs
public interface IRepository where T : class
{
Task GetByIdAsync(int id);
Task> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
// UserRepository.cs
public class UserRepository : IRepository
{
private readonly DbContext _context;
public UserRepository(DbContext context) => _context = context;
public async Task GetByIdAsync(int id) =>
await _context.Users.FindAsync(id);
public async Task> GetAllAsync() =>
await _context.Users.ToListAsync();
// ... other methods
}
// Easy to mock in tests
public class FakeUserRepository : IRepository
{
private readonly List _users = new();
// ... in-memory implementation
}
Why This Helps
- Decouples business logic from data access
- Enables easy unit testing with mocks
- Allows swapping data stores without code changes
How to Test
- Unit test services with mock repository
- Integration test repository against real DB
When to Use
Any application with data persistence. Essential for clean architecture.
Performance/Security Notes
Don’t over-abstract. Generic repositories can become leaky abstractions for complex queries.
References
Try this tip in your next project and share your results in the comments!
Discover more from Code, Cloud & Context
Subscribe to get the latest posts sent to your email.