TGViewer
Библиотека шарписта | C#, F#, .NET, ASP.NET Библиотека шарписта | C#, F#, .NET, ASP.NET @csharpproglib · 21.7K subscribers
Post #6987 2.47K
✏️ Паттерн репозиторий: неочевидные моменты

Репозиторий скрывает логику работы с данными за интерфейсом. Контроллер или сервис не знает, откуда берётся объект: из базы, кеша или файла. Это удобно, но у паттерна есть нюансы, о которых редко говорят.

Зачем он вообще нужен

Без репозитория бизнес-логика тесно связана с DbContext. Тесты требуют реальной базы или громоздких моков. При смене ORM надо переписывать половину приложения.

Репозиторий даёт одну точку входа для работы с конкретной сущностью и позволяет подменять реализацию без изменения вызывающего кода.

Неочевидный момент 1: SaveChanges внутри репозитория

Если вызывать SaveChangesAsync() в каждом методе репозитория, теряется возможность атомарно сохранить несколько операций. Например, создать пользователя и сразу выдать ему роль в одной транзакции не получится.

Решение: убрать SaveChanges из репозитория и перенести его в Unit of Work или вызывать явно на уровне сервиса.
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
IRoleRepository Roles { get; }
Task<int> SaveChangesAsync();
}

public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;

public IUserRepository Users { get; }
public IRoleRepository Roles { get; }

public UnitOfWork(AppDbContext context)
{
_context = context;
Users = new UserRepository(context);
Roles = new RoleRepository(context);
}

public async Task<int> SaveChangesAsync()
=> await _context.SaveChangesAsync();

public void Dispose() => _context.Dispose();
}


Неочевидный момент 2: Generic-репозиторий не всегда помогает

Соблазн велик написать один GenericRepository<T> и использовать везде:
public class GenericRepository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _context;
protected readonly DbSet<T> _dbSet;

public GenericRepository(AppDbContext 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);
}


Проблема в том, что специфичные запросы всё равно придётся добавлять. Найти пользователя по почте через универсальный метод не выйдет без дополнительных расширений. В итоге generic-репозиторий обрастает методами и теряет смысл.

Лучше наследовать от него там, где действительно нет специфики, а сложные сущности описывать отдельными интерфейсами:
public interface IUserRepository : IRepository<User>
{
Task<User?> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetActiveUsersAsync();
}

public class UserRepository : GenericRepository<User>, IUserRepository
{
public UserRepository(AppDbContext context) : base(context) { }

public async Task<User?> GetByEmailAsync(string email)
=> await _dbSet.FirstOrDefaultAsync(u => u.Email == email);

public async Task<IEnumerable<User>> GetActiveUsersAsync()
=> await _dbSet.Where(u => u.IsActive).ToListAsync();
}


Неочевидный момент 3: EF Core уже репозиторий

DbContext и DbSet<T> это и есть реализация паттерна репозиторий. DbSet — коллекция объектов, DbContext — Unit of Work. Оборачивать EF Core в ещё один слой репозитория иногда только добавляет лишний код.

Это оправдано, когда нужно:
- тестировать бизнес-логику без базы (мок интерфейса проще, чем мок DbContext)
- поддерживать несколько источников данных
- изолировать слои приложения архитектурно

Если проект небольшой и планов менять ORM нет, прямое использование DbContext в сервисах может быть вполне нормальным решением.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
  • ❤ 3
  • 😁 3
  • 💯 3
  • 👍 2
  • 🥱 1
More from @csharpproglib
  1. Sep 23, 2026⚙️ yield return не бесплатный Итераторы выглядят просто, но работают иначе: IEnumerable<in…
  2. Sep 22, 2026💡 Replace, Regex или StringBuilder? Для замены текста в C# есть несколько инструментов. И…
  3. Sep 21, 2026⚙️ Настоящие атомарные операции Если Volatile решает проблему видимости, то Interlocked ре…
  4. Sep 20, 2026💪 Разминка перед трудовыми буднями Что произойдёт? ❤️ — список станет [1, 3] 🔥 — Invalid…
  5. Sep 19, 2026🧩 Middleware в ASP.NET Core: 3 ловушки Middleware — звено HTTP pipeline: app.Use(async (c…
  6. Sep 18, 2026📍 Навигация: Вакансии • Задачи • Собесы 🐸Библиотека шарписта #garbage_collector
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →