Ответ
Миграции — это краеугольный камень практики Database-as-Code. Мы использовали миграции EF Core для гарантии идентичности схемы БД на всех стендах (dev, test, production) и ведения истории изменений.
Рабочий процесс (Development)
- Изменение модели данных в классах C# (сущностях DbContext).
- Создание миграции: Эта команда сравнивает текущую модель с состоянием БД и генерирует файлы миграции.
dotnet ef migrations add AddUserEmailAndCreatedTimestamp # Создаст файлы: 20240515120000_AddUserEmailAndCreatedTimestamp.cs -
Проверка сгенерированного кода: Всегда просматривайте файл миграции
Up()иDown()методы. Иногда требуется ручная корректировка (например, для сложных индексов или данных по умолчанию).public partial class AddUserEmailAndCreatedTimestamp : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn<string>( name: "Email", table: "Users", type: "text", nullable: false, defaultValue: ""); migrationBuilder.AddColumn<DateTime>( name: "CreatedAt", table: "Users", type: "timestamp with time zone", nullable: false, defaultValueSql: "NOW()"); // Пример использования SQL-выражения } // ... метод Down для отката } - Обновление локальной БД:
dotnet ef database update
Интеграция в CI/CD (Production)
Прямой вызов dotnet ef в production нежелателен. Вместо этого:
- Генерация SQL-скрипта: На этапе сборки создается idempotent-скрипт, который можно проверить.
dotnet ef migrations script --idempotent --output migrations.sql - Применение через инструменты БД: Сгенерированный
migrations.sqlприменяется к production-БД с помощью надежных инструментов (например, Flyway, Liquibase, или через пайплайн DevOps с проверкой DBA). Это дает контроль, возможность отката и аудит.
Критически важные практики:
- Миграции должны быть обратимыми (
Downметод). Это важно для отката неудачных релизов. - Не редактируйте файлы миграций после того, как они применены к общей БД. Создавайте новую миграцию.
- Для seed-данных используйте отдельные миграции или кастомную логику инициализации, проверяющую наличие данных.
- Всегда тестируйте миграции на копии production-базы перед деплоем.
Ответ 18+ 🔞
Давай разберем эту всю хуйню про миграции, чтобы у тебя в голове не каша была, а понимание, как это внатуре работает.
Смотри, миграции — это типа священный грааль, когда ты делаешь базу как код. Без них — пиздец и бардак: у тебя на локале одна схема, на тестовом — другая, а на прод влетает третья, и всё падает с душераздирающими ошибками про отсутствующие колонки. Мы на EF Core отбили все зубы, чтобы такого не было. Суть в том, что история всех изменений базы — это просто папка с файликами в твоём репозитории. Красота, а не жизнь.
Как с этим жить, когда пишешь фичу (Development)
- Ты че-то там накосячил в моделях. Добавил поле
Emailпользователю или, там, новую таблицуKarmaPoints. Обычные дела. - Генеришь миграцию. Ты не пишешь SQL руками, как последний лузер. Ты даёшь команду EF Core, чтобы он сам, умный такой, сравнил твои классы C# с тем, что сейчас в базе, и нагенерил разницу.
dotnet ef migrations add AddUserEmailAndCreatedTimestampОн создаст файл с именем вроде
20240515120000_AddUserEmailAndCreatedTimestamp.cs. Циферки в начале — это timestamp, чтобы порядок соблюдался, а то будет ебучка. -
Смотришь, что этот умник нагенерил. Это ОБЯЗАТЕЛЬНО. Потому что EF Core иногда такое выдумает, что волосы дыбом. Откроешь файл, а там в
Up()методе какая-нибудь дичь. ИлиDown()метод для отката кривой. Поправить надо сразу.public partial class AddUserEmailAndCreatedTimestamp : Migration { protected override void Up(MigrationBuilder migrationBuilder) { // Смотри, он хочет добавить колонку Email. Вроде норм. migrationBuilder.AddColumn<string>( name: "Email", table: "Users", type: "text", nullable: false, defaultValue: ""); // А вот дефолтное значение пустой строки — окей. // А тут добавляет CreatedAt со значением NOW() из самой базы. Уже лучше, чем C#-овый DateTime.UtcNow в коде миграции. migrationBuilder.AddColumn<DateTime>( name: "CreatedAt", table: "Users", type: "timestamp with time zone", nullable: false, defaultValueSql: "NOW()"); } // ... а тут должен быть метод Down(), который это всё откатывает. Если его нет или он хуёвый — пиши пропало. } - Запускаешь это добро на свою локальную базу. Просто, как два байта:
dotnet ef database updateИ всё, база обновилась. Теперь у тебя и в коде, и в базе одно и то же. Можно тестировать.
А вот как это всё должно ебашить на проде, чтобы не обосраться (CI/CD)
Тут уже не до шуток. Ты не будешь же на продакшен-сервере, где база на терабайты, запускать dotnet ef database update! Это уровень дилетанта, который хочет всё сломать.
- Готовим скрипт. На этапе сборки (в CI) мы из этих файлов-миграций генерируем один большой, но идемпотентный SQL-скрипт. Это значит, что его можно нахуй применять сколько угодно раз — если изменение уже есть, оно не выполнится второй раз.
dotnet ef migrations script --idempotent --output migrations.sqlПолучаем файл
migrations.sql. Его можно открыть, показать своему DBA (если он у вас есть, а не вы сами себе DBA) и сказать: "Мужик, смотри, что мы будем в базу пихать". - Применяем с умом. Этот скрипт мы не запускаем тупо из пайплайна. Мы используем специальные, взрослые инструменты: Flyway, Liquibase или хотя бы прогоняем через шаг деплоя, где есть ручное подтверждение и откат. Это даёт тебе контроль, аудит и спокойный сон.
Железные правила, которые нарушать — себя не уважать:
- Метод
Down()— это святое. Он должен чисто откатывать то, что делаетUp(). Представь, что релиз пошёл по пизде и надо откатиться на прошлую версию приложения. Без рабочегоDown()ты в глубокой жопе. - Не лезь в старые миграции, которые уже ушли на общие базы. Ты их отредактируешь, а у коллег или на других стендах уже другая история. Полный пиздец и рассинхрон. Нужно новое изменение — создавай новую миграцию.
- Чтобы накатить тестовые данные (seed), не пихай их прямо в миграции. Либо делай отдельную, чисто дата-миграцию, либо пиши скрипт, который умно проверяет, есть ли уже данные, прежде чем их вставить.
- И БОГ ТЕБЕ В ПОМОЩЬ, ТЕСТИРУЙ ЭТИ МИГРАЦИИ НА КОПИИ ПРОД БАЗЫ! Перед тем как лезть в боевую, скопируй её куда-нибудь и прогнай там свои скрипты. Иначе можно получить "ой, а мы не думали, что на проде 100 миллионов записей, и наша миграция с добавлением индекса будет выполняться 8 часов, положив сервис". Удивление будет пиздец.