Entity FrameworkJuniorCoding

Что такое DbSet<T> и как его использовать?

DbSet<T> — это типизированный шлюз к таблице базы данных внутри DbContext. Через него выполняются LINQ-запросы, добавление, обновление и удаление сущностей типа T.

Что такое DbSet<T>

DbSet<T> — свойство DbContext, представляющее коллекцию всех сущностей типа T в базе данных. Он реализует IQueryable<T>, поэтому LINQ-выражения транслируются в SQL и выполняются на стороне базы данных, а не в памяти.

Типичные операции:

  • Чтение: FindAsync, FirstOrDefaultAsync, ToListAsync, Where, Include
  • Добавление: Add / AddAsync / AddRange
  • Удаление: Remove / RemoveRange
  • Изменение выполняется через свойства отслеживаемой сущности — EF Core фиксирует разницу сам.

Объявление в DbContext

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    // EF Core 6+ — рекомендуемый синтаксис
    public DbSet<Product> Products => Set<Product>();
    public DbSet<Order>   Orders   => Set<Order>();
}

Основные операции

// 1. Получить по первичному ключу (сначала смотрит в кэш, потом в БД)
var product = await db.Products.FindAsync(productId);

// 2. Фильтрация — SQL WHERE на стороне БД
var cheap = await db.Products
    .Where(p => p.Price < 1000 && p.IsAvailable)
    .OrderBy(p => p.Price)
    .ToListAsync();

// 3. Добавление новой записи
var newProduct = new Product { Name = "Widget", Price = 499 };
db.Products.Add(newProduct);
await db.SaveChangesAsync(); // INSERT выполняется здесь

// 4. Обновление
var existing = await db.Products.FindAsync(productId);
if (existing is not null)
{
    existing.Price = 599;   // Change Tracker зафиксирует изменение
    await db.SaveChangesAsync(); // UPDATE
}

// 5. Удаление
var toDelete = await db.Products.FindAsync(productId);
if (toDelete is not null)
{
    db.Products.Remove(toDelete);
    await db.SaveChangesAsync(); // DELETE
}

AsNoTracking для readonly-запросов

// Если данные нужны только для отображения — отключите отслеживание.
// Это быстрее и потребляет меньше памяти.
var catalog = await db.Products
    .AsNoTracking()
    .Where(p => p.CategoryId == categoryId)
    .ToListAsync();

Подводные камни

  • LINQ не всегда транслируется в SQL — вызов методов C# внутри Where (например, .ToString(), пользовательские методы) вызовет InvalidOperationException или загрузит все строки в память. Проверяйте сгенерированный SQL через LogTo или ToQueryString().
  • Материализация происходит при ToList/FirstOrDefault — до этого момента запрос — это только выражение. Добавление Where после ToListAsync() фильтрует уже загруженные данные в памяти.
  • N+1 запросов — обращение к навигационным свойствам в цикле без Include генерирует отдельный SQL на каждую итерацию.
  • Add vs AttachAdd помечает сущность как Added (будет INSERT). Если сущность уже есть в БД, используйте Attach + изменение свойств, иначе получите дубликат или ошибку уникальности.
  • Изменение detached-объекта — если объект получен вне текущего контекста (например, десериализован из JSON), Change Tracker о нём не знает. Нужно явно вызвать db.Update(entity) или db.Entry(entity).State = EntityState.Modified.
  • Параллельные запросы к одному контекстуDbSet не потокобезопасен. Нельзя запускать два await db.X.ToListAsync() через Task.WhenAll на одном DbContext.
  • Отсутствие SaveChangesAsyncAdd/Remove только меняют состояние в трекере. Без SaveChangesAsync() изменения не попадут в базу данных.
  • Remove без загрузки — чтобы удалить без предварительного чтения, используйте db.Products.Where(p => p.Id == id).ExecuteDeleteAsync() (EF Core 7+), а не загрузку + Remove.

Common mistakes

  • Путать DbSet как типизированный вход в запросы и изменения с похожим механизмом из другой версии или платформы.
  • Игнорировать runtime-границы Entity Framework: lifecycle, DI scope, SQL translation, UI thread или platform API.
  • Не обсуждать null/empty/error cases и поведение под нагрузкой.

What the interviewer is testing

  • Кандидат объясняет DbSet как типизированный вход в запросы и изменения на конкретном примере, а не только определением.
  • Указывает последствия для производительности, тестируемости и поддержки.
  • Различает документированное поведение текущего стека и устаревшие практики.

Sources

Related topics