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 Byte Architect
Subscribe to get the latest posts sent to your email.