Ответ
Generic Repository — это реализация паттерна Repository, которая использует обобщённые типы (Generics) для создания единого, переиспользуемого класса, абстрагирующего операции доступа к данным (CRUD) для любой сущности предметной области.
Зачем это нужно? Чтобы избежать дублирования кода. Вместо создания отдельного репозитория для Product, Order, Customer и т.д., создаётся один общий репозиторий, работающий с типом TEntity.
Базовая реализация с Entity Framework Core:
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
Task SaveChangesAsync();
}
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(DbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public void Update(T entity) => _dbSet.Update(entity);
public void Delete(T entity) => _dbSet.Remove(entity);
public async Task SaveChangesAsync() => await _context.SaveChangesAsync();
}
// Использование
public class ProductService
{
private readonly IRepository<Product> _productRepo;
public ProductService(IRepository<Product> productRepo) => _productRepo = productRepo;
public async Task<Product?> GetProduct(int id) => await _productRepo.GetByIdAsync(id);
}
Преимущества:
- Универсальность: Один класс для всех сущностей.
- Сокращение кода: Устраняет шаблонный код для базовых операций.
- Простота тестирования: Легко подменить моком через интерфейс.
Недостатки и предостережения:
- Ограниченность: Сложные запросы с джойнами, фильтрацией или агрегацией могут не вписаться в общий шаблон. Часто это решают, либо возвращая
IQueryable<T>из репозитория (что может нарушить инкапсуляцию), либо используя дополнительный паттерн Specification. - Антипаттерн? В простых CRUD-приложениях Generic Repository может быть излишним, добавляя ненужный слой абстракции поверх
DbSet. Его ценность возрастает в крупных проектах с чётким разделением слоёв и сложной бизнес-логикой.
Ответ 18+ 🔞
Ну что за мода пошла — на каждый чих городить репозитории отдельные? Сидишь, пишешь для Product одно и то же, для Order — то же самое, для Customer — опять этот блядский CRUD. Рука отваливается, честно. А потом ещё тесты для каждого писать, мокать это всё… Пиздец, а не жизнь.
Вот поэтому умные дядьки и придумали Generic Repository — одну хуйню, которая работает с любыми сущностями. Типа универсальный пульт для управления данными. Выглядит примерно так:
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
Task SaveChangesAsync();
}
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext _context;
protected readonly DbSet<T> _dbSet;
public Repository(DbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public void Update(T entity) => _dbSet.Update(entity);
public void Delete(T entity) => _dbSet.Remove(entity);
public async Task SaveChangesAsync() => await _context.SaveChangesAsync();
}
// Использование
public class ProductService
{
private readonly IRepository<Product> _productRepo;
public ProductService(IRepository<Product> productRepo) => _productRepo = productRepo;
public async Task<Product?> GetProduct(int id) => await _productRepo.GetByIdAsync(id);
}
Смотри, какой красивый блять — один класс на все случаи жизни. Хочешь работать с продуктами — Repository<Product>, хочешь с заказами — Repository<Order>. Всё, больше нихуя не нужно. Код в разы меньше, тестить проще, подменить моком — вообще как два пальца.
Но, как всегда, есть одно огромное НО. Если тебе вдруг понадобится какой-то хитрый запрос — с джойнами, группировками, или фильтрацией по трём полям сразу — тут этот универсальный репозиторий может показать тебе фигу. Он для базовых операций хорош, а для сложной логики — уже не очень. Приходится либо IQueryable наружу высовывать (что само по себе пиздец как некрасиво), либо городить отдельный паттерн Specification, что тоже не сахар.
И ещё момент: если у тебя приложение — три экрана и база на пять таблиц, то этот Generic Repository — просто лишняя прослойка, которая только мозги парит. Ты можешь спокойно работать прямо с DbSet и не ебаться. А вот в больших проектах, где слоёв как в матрёшке, и бизнес-логика сложнее, чем инструкция к стиральной машине — там он реально может жизнь упростить.
Так что думай сам, надо тебе это или нет. Иногда проще не умничать, а делать как работает.