EF Core: сущности и DbContext

EF Core: работа с базой данных на C# без ручного SQL.

Суть: Entity Framework Core (EF Core) — это ORM (Object-Relational Mapper): он связывает C#-классы с таблицами БД. Вы пишете запросы на C# (LINQ), а EF Core генерирует SQL, выполняет его и превращает строки обратно в объекты.

Без ORM работа с БД — это руками писать SQL-строки, открывать соединения, читать ридеры и маппить колонки в поля. Долго и хрупко. EF Core берёт это на себя: вы описываете модель классами, а он общается с базой за вас.

Сущности и DbContext

public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public string Email { get; set; } = "";
}

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

    public DbSet<User> Users => Set<User>();
}

Класс User — это сущность, она станет таблицей. AppDbContext — точка входа к БД; свойство DbSet<User> представляет таблицу пользователей, через которое идут запросы.

Регистрация

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));

Строка подключения берётся из appsettings.json. UseNpgsql — для PostgreSQL; есть провайдеры для SQL Server (UseSqlServer), SQLite и других.

Как работает под капотом

EF Core строит модель из ваших классов: по соглашениям Id становится первичным ключом, типы свойств — типами колонок. DbContext внутри хранит change tracker — он отслеживает загруженные объекты и фиксирует изменения. Когда вы вызываете SaveChanges(), EF сравнивает текущее состояние объектов с исходным и генерирует нужные INSERT/UPDATE/DELETE. Путь запроса к данным выглядит так:

[Контроллер]
     |
     v  LINQ-запрос
[DbContext / DbSet]
     |
     v  EF Core строит SQL
[Провайдер (Npgsql)]
     |
     v  SQL по сети
[База данных]
     |
     v  строки таблицы
[EF маппит строки -> объекты User]
     |
     v
[Контроллер отдаёт JSON]

Частые ошибки

  • Регистрировать DbContext как Singleton. Он не потокобезопасен и должен жить один запрос (Scoped) — об этом отдельный урок.
  • Класть строку подключения в код. Она содержит логин/пароль — место ей в конфиге/секретах.
  • Думать, что EF — это «магия без SQL». SQL всё равно генерируется; полезно уметь его смотреть и понимать.

Best practices

  • Держите сущности чистыми (POCO) — без логики работы с БД внутри.
  • Выбирайте провайдер под вашу БД; для учебных проектов удобен SQLite (файл вместо сервера).
  • Включайте логирование SQL в dev, чтобы видеть, какие запросы реально уходят в базу.

Соглашения, которыми EF строит модель

EF Core придерживается принципа «convention over configuration»: многое выводится по умолчанию из ваших классов. Свойство Id или <Тип>Id становится первичным ключом. Имя DbSet или класса даёт имя таблицы. Тип C#-свойства превращается в тип колонки (string в text/nvarchar, int в integer). Навигационные свойства и внешние ключи распознаются по именам. Там, где соглашений мало, подключают Fluent API в методе OnModelCreating или атрибуты — для индексов, ограничений длины, точных типов и связей.

Под капотом DbContext — это и единица работы (Unit of Work), и набор репозиториев (DbSet'ы). Он собирает изменения в памяти и применяет их одним SaveChanges в транзакции. Поэтому правильно держать DbContext коротким — на один запрос или одну логическую операцию, а не делить между запросами.

Выбор провайдера и связь с БД

EF Core не привязан к одной СУБД: провайдеры подключаются пакетами — UseNpgsql для PostgreSQL, UseSqlServer для SQL Server, UseSqlite для SQLite, есть и другие. Большая часть вашего кода (сущности, LINQ-запросы, миграции) не зависит от выбора, что упрощает миграцию между базами и тестирование. Для учебных проектов SQLite особенно удобен: вся база — один файл, не нужно поднимать сервер.

Строка подключения — чувствительные данные (хост, логин, пароль), поэтому ей место не в коде, а в конфигурации и секретах. В dev её держат в User Secrets, в проде — в переменных окружения. Включение логирования SQL в Development бесценно: вы видите ровно тот SQL, который EF генерирует из ваших LINQ-запросов, и быстро замечаете неэффективные запросы и лишние обращения к БД ещё до того, как они станут проблемой в проде.

Итог: EF Core связывает классы и таблицы, а DbContext — это ваш шлюз к БД с трекингом изменений. Дальше — как создать схему БД через миграции.

Проверьте себя
1. Что такое DbContext в EF Core?
AЭто таблица в базе
BТочка входа к БД, которая содержит DbSet-ы и отслеживает изменения объектов
CЭто строка подключения
DЭто контроллер
2. Что делает EF Core при вызове SaveChanges()?
AУдаляет базу данных
BСравнивает состояние отслеживаемых объектов с исходным и генерирует INSERT/UPDATE/DELETE
CТолько читает данные
DСоздаёт новую миграцию