Зачем использовать репозитории вместе с Entity Framework
Что проверяют
Понимаете ли вы, что DbContext сам по себе уже реализует Unit of Work, а DbSet<T> работает как репозиторий. Поэтому вопрос не «зачем репозитории», а когда дополнительный слой поверх EF Core оправдан и когда он только мешает. Следом часто просят показать, где generic-репозиторий начинает вредить.
Как отвечать
Начните с того, что EF Core уже даёт: отслеживание изменений, транзакцию на SaveChanges, LINQ-запросы с Include, AsNoTracking и проекциями через Select. Обёртка поверх этого нужна не всегда. Когда она оправдана:
- репозиторий агрегата в DDD: не на каждую таблицу, а на корень агрегата, например
IOrderRepositoryсGetByIdAsyncиAdd. Он гарантирует, что заказ всегда загружается целиком со строками, и прячет эти детали от домена; - независимость домена: в Clean Architecture интерфейс лежит в доменном слое, реализация на EF в инфраструктуре, и доменный проект не ссылается на EF;
- переиспользуемые сложные запросы: чтобы одна выборка не копировалась по десятку сервисов. Альтернатива здесь паттерн спецификаций, например библиотека Ardalis.Specification.
Когда не нужен: простое CRUD-приложение или сторона чтения в CQRS, где обработчик запроса напрямую работает с DbContext или Dapper и возвращает DTO.
Отдельно про тесты. Аргумент «репозиторий легко замокать» верен для юнит-тестов бизнес-логики, но сами запросы надо проверять на реальной базе. Microsoft не рекомендует для этого провайдер InMemory: он не повторяет поведение реляционной базы. Лучше настоящая база в Testcontainers или хотя бы SQLite в памяти.
Где спотыкаются
Делают IRepository<T> с методом GetAll(), возвращающим IEnumerable<T>. Фильтр потом выполняется в памяти, и в приложение уезжает вся таблица.
Возвращают из репозитория IQueryable<T>. Абстракция протекает: вызовы EF расползаются по сервисам, а смысл слоя пропадает.
Вызывают SaveChanges внутри каждого метода репозитория. Две операции из разных репозиториев перестают быть одной транзакцией, хотя DbContext давал это бесплатно.