Миграции

Миграции: версионирование схемы базы данных вместе с кодом.

Суть: миграция — это C#-описание изменения схемы БД (создать таблицу, добавить колонку). EF Core генерирует миграции из ваших классов, а команда применяет их к базе. Так схема версионируется в git вместе с кодом.

Когда вы добавили класс User, в базе ещё нет таблицы. Создавать её руками SQL-скриптами — несинхронно с кодом и опасно. Миграции решают это: изменили модель, сгенерировали миграцию, применили. История изменений хранится в проекте.

Рабочий цикл

# один раз: установить инструмент
dotnet tool install --global dotnet-ef

# создать миграцию по текущей модели
dotnet ef migrations add InitialCreate

# применить миграции к базе
dotnet ef database update

Первая команда создаёт папку Migrations/ с C#-файлом: в нём методы Up() (как применить) и Down() (как откатить). Вторая команда выполняет SQL в базе и записывает, какие миграции уже применены, в служебную таблицу __EFMigrationsHistory.

Эволюция схемы

Изменили модель (добавили поле Age)
        |
        v
dotnet ef migrations add AddAge   ->  Migrations/..._AddAge.cs
        |
        v
dotnet ef database update         ->  ALTER TABLE Users ADD Age int
        |
        v
__EFMigrationsHistory пополнилась записью

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

EF Core хранит «снимок» модели (ModelSnapshot). При генерации новой миграции он сравнивает текущие классы со снимком и вычисляет дельту — что изменилось. Эта дельта превращается в код Up()/Down(). При database update EF смотрит в __EFMigrationsHistory, какие миграции ещё не применены, и выполняет их по порядку. Откат запускает метод Down() соответствующей миграции.

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

  • Менять уже применённую миграцию. Если она применена на других машинах/в проде, правка ломает синхронизацию. Делайте новую миграцию.
  • Не коммитить папку Migrations. Миграции — часть истории схемы, их обязательно версионируют в git.
  • Применять EnsureCreated вместе с миграциями. Это разные механизмы, их нельзя смешивать в одном проекте.

Best practices

  • Давайте миграциям осмысленные имена: AddUserEmail, а не Migration1.
  • Просматривайте сгенерированный код миграции перед применением — EF не всегда угадывает намерение.
  • В продакшене применяйте миграции контролируемо (скриптом dotnet ef migrations script или в пайплайне), а не автоматически при старте без бэкапа.

Анатомия файла миграции

Каждая миграция — это класс с двумя методами. Up() описывает, как привести схему к новому состоянию (создать таблицу, добавить колонку, индекс), Down() — как откатить это изменение обратно. Рядом EF хранит ModelSnapshot — слепок текущей модели целиком. Когда вы генерируете новую миграцию, EF сравнивает актуальные классы со снимком, вычисляет разницу и пишет в Up() только её. Снимок после этого обновляется. Именно поэтому миграции применяются строго по порядку — каждая опирается на состояние после предыдущей.

Служебная таблица __EFMigrationsHistory в самой базе хранит список уже применённых миграций. При database update EF читает её, видит, чего ещё нет, и докатывает недостающее. Так одна и та же команда безопасно работает и на чистой базе (применит всё), и на уже частично обновлённой (применит только новое).

Миграции в команде и в проде

Поскольку миграции версионируются в git, в команде они работают как общая история схемы: коллега подтянул ваши изменения, выполнил database update — и его локальная БД совпала с вашей. Конфликты решаются как с любым кодом, а золотое правило — не редактировать применённую миграцию. Если изменение уже у других или в проде, правка рассинхронизирует состояние; вместо этого создают новую миграцию поверх.

В продакшене миграции применяют осознанно. Автоприменение при старте приложения удобно для учебных проектов, но опасно для боевых: при нескольких инстансах возможны гонки, а ошибочная миграция способна повредить данные. Зрелый подход — генерировать SQL-скрипт через dotnet ef migrations script и применять его контролируемо в пайплайне деплоя, после бэкапа и ревью. Так изменение схемы становится предсказуемым шагом релиза, а не сюрпризом.

Итог: миграции версионируют схему БД вместе с кодом и применяют изменения предсказуемо. Дальше — как читать и писать данные через LINQ.

Проверьте себя
1. Зачем нужны миграции в EF Core?
AЧтобы ускорить запросы
BЧтобы версионировать изменения схемы БД вместе с кодом и применять их предсказуемо
CЧтобы заменить SQL навсегда
DЧтобы валидировать DTO
2. Что делать, если миграция уже применена в проде, а схему надо изменить?
AОтредактировать существующую миграцию
BСоздать новую миграцию с нужным изменением
CУдалить базу
DПоменять ModelSnapshot вручную