Миграции
Миграции: версионирование схемы базы данных вместе с кодом.
Суть: миграция — это 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.