migrations.md 13 KB

Системы миграции данных

Модель данных в процессе разработки имеет свойство изменяться, и в какой-то момент она перестает соответствовать базе данных. Функция системы миграции сводится к тому, чтобы синхронизировать структуру БД с моделью данных в приложении без потери существующих данных.

Не помню, затрагивали ли мы этот момент при выборе фреймворка для работы с БД, на всякий случай расскажу еще раз.

Существует два варианта синхронизации модели и базы:

  • Code First - "сначала код", то есть мы описываем модель и структура базы данных меняется в соответствии с моделью (мы этот подход не используем, он применяется в Entity Framework)
  • DB First - "база данных первична", то есть мы сначала меняем структуру базы данных и только потом приводим модель в соответствие с ней. Это как раз наш случай.

Для того, чтобы структура базы данных менялась в нужных версиях приложений (раньше в принципе можно, но точно не позже) существуют системы миграции, рассмотрим наиболее популярные:

Взято отсюда

  • EF Core Migrations

    Как видно по названию, является частью Entity Framework. Нативный инструмент, работающий из коробки без каких-либо плясок с бубном. Большое количество документации, официальной и не очень, простота и т.д.

    Имеет смысл, если используется Entity Framework.

  • RoundhousE

    Этот инструмент управления миграциями, распространяемый под лицензией Apache 2.0, работает на движке T-SQL миграций (то есть для миграции пишутся DDL-скрипты).

  • ThinkingHome.Migrator

    Инструмент для версионной миграции схемы базы данных под платформу .NET Core, распространяемый под лицензией MIT.

    В целом, проект выглядит перспективным, особенно если развивался бы.

  • Fluent Migrator

    Наиболее популярный инструмент миграций, имеющий большую армию поклонников. Распространяется под лицензией Apache 2.0. Как указано в описании, является платформой миграции для .NET, аналогичная Ruby on Rails Migrations. Изменения схемы БД описываются в классах на C#.

    Мне он не понравился из-за того, что нужно писать классы для миграций, а это так называемые "проектные знания", которыми не охота вас загружать

  • DBup

    DbUp – это библиотека на .NET, которая помогает накатывать изменения на сервер SQL. Она отслеживает, какие скрипты изменений уже выполнены, и запускает те, которые необходимы для обновления БД. Библиотека выросла из проекта опенсорсного движка блогов на ASP.NET и существует под лицензией MIT, а код лежит на GitHub’е. Миграции описываются с помощью T-SQL.

    ИМХО идеальный вариант для наших ПЕТ- проектов: не нужно изучение лишних сущностей, как в Fluent Migrator, достаточно знания SQL

Добавление возможности миграции в АПИ

В десктопном приложении использовать миграции можно, но не нужно - нет гарантии, что не запустятся одновременно две копии программы. Правильнее миграции накатывать либо администратору БД (скрипты мы в любом случае будем писать), либо при запуске новой версии АПИ (этот вариант предпочтительнее, так как исключается человеческий фактор - миграция применится в тот момент, когда она нужна)

Делал по этому туториалу

  1. В проекте с АПИ созадем каталог migrations, где будем хранить скрипты для миграции
  2. В настройках проекта (файл *.csproj) укажем, чтобы все SQL-файлы из этого каталога включались в исполняемый файл

    <ItemGroup>
        ...
        <EmbeddedResource Include="migrations/*.sql" />
    </ItemGroup>
    
  3. Создадим файл для миграции migrations/agent_type.sql

    Вообще миграции предназначены для изменения структуры БД, но никто не запрещает нам выполнять и DML- скрипты, например, для заполнения словарей

    insert into AgentType values
        (1, "индивидуальный предприниматель"),
        (2, "Общество с ограниченной ответственностью");   
    
  4. Устанавливаем библиотеку DBup

    Можно через Nuget, а можно и в консоли:

    dotnet add package dbup-mysql
    
  5. Дописываем в начало нашего Program.cs код для накатывания миграций

    // проверяет наличие возможности подключения к БД
    EnsureDatabase.For.MySqlDatabase(connectionString);
    
    // подгатавливает миграцию
    // куда и что будем накатывать
    var upgrader = DeployChanges.To
        .MySqlDatabase(connectionString)
        .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
        .LogToConsole()
        .Build();
    
    // запуск миграции
    var result = upgrader.PerformUpgrade();
    
    // проверка результата миграции
    if (!result.Successful)
    {
        throw new Exception("Не смог сделать миграцию");
    }
    

    Если все сделали правильно, то в консоли увидите примерно такие логи

    2025-09-18 10:31:43 +03:00 [INF] Beginning database upgrade
    2025-09-18 10:31:43 +03:00 [INF] Checking whether journal table exists
    2025-09-18 10:31:43 +03:00 [INF] Journal table does not exist
    2025-09-18 10:31:43 +03:00 [INF] Executing Database Server script 'api_cs.migrations.agent_type.sql'
    2025-09-18 10:31:43 +03:00 [INF] Checking whether journal table exists
    2025-09-18 10:31:43 +03:00 [INF] Creating the `schemaversions` table
    2025-09-18 10:31:44 +03:00 [INF] The `schemaversions` table has been created
    2025-09-18 10:31:44 +03:00 [INF] Upgrade successful
    

    А в базе добавится таблица shemaversions, в которой хранится какие миграции и когда применялись (чтобы повторно не накатить уже выполненную миграцию)

DDL- запросы для изменения структуры базы данных

Вообще с DDL мы еще не работали (базу разворачивали из готового скрипта), напомню какие команды относятся к DDL:

  • CREATE – используется для создания объектов базы данных;
  • ALTER – используется для изменения объектов базы данных;
  • DROP – используется для удаления объектов базы данных.

Добавление таблиц

В принципе структуру запроса можно и не запоминать - вы всегда можете в MySQL Workbench или DBeaver получить DDL скрипт для существующей таблицы, например AgentType:

CREATE TABLE `AgentType` (
  `ID` int NOT NULL AUTO_INCREMENT,
  `Title` varchar(50) NOT NULL,
  `Image` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`ID`)
)

Думаю тут все более менее очевидно - задается название таблицы и описываются ее поля

  • параметр NOT NULL означает, что поле обязательное
  • параметр DEFAULT <value> задает значение по-умолчанию

Рассмотрим DDL скрипт таблицы Agent - тут у нас появляются внешние ключи:

CREATE TABLE `Agent` (
  `ID` int NOT NULL AUTO_INCREMENT,
  `Title` varchar(150) NOT NULL,
  `AgentTypeID` int NOT NULL,
  `Address` varchar(300) DEFAULT NULL,
  `INN` varchar(12) NOT NULL,
  `KPP` varchar(9) DEFAULT NULL,
  `DirectorName` varchar(100) DEFAULT NULL,
  `Phone` varchar(20) NOT NULL,
  `Email` varchar(255) DEFAULT NULL,
  `Logo` varchar(100) DEFAULT NULL,
  `Priority` int NOT NULL,
  PRIMARY KEY (`ID`),
  CONSTRAINT `FK_Agent_AgentType` FOREIGN KEY (`AgentTypeID`) REFERENCES `AgentType` (`ID`)
);
  • поле для внешнего ключа (AgentTypeID) создается как обычно
  • и записывается ограничение (CONSTRAINT), в котором мы описываем, что поле является внешним ключем (FOREIGN KEY (`AgentTypeID`)) и ссылается на таблицу и поле в ней (REFERENCES `AgentType` (`ID`))

Изменение полей существующей таблицы

Добавление поля

ALTER TABLE table_name
ADD COLUMN new_column_name data_type [constraints];

Переименовывание поля

  • универсальный вариант, меняющий и название поля и его тип

    ALTER TABLE имя_таблицы
    CHANGE старое_имя новое_имя ТИП_ДАННЫХ;
    
  • простой вариант, меняющий только название поля

    ALTER TABLE имя_таблицы
    RENAME COLUMN старое_имя TO новое_имя;
    

Удаление поля

ALTER TABLE table_name
DROP COLUMN column_name;

Изменение типа данных или ограничений поля

ALTER TABLE table_name
MODIFY COLUMN column_name new_data_type [new_constraints];

Добавление внешнего ключа

ALTER TABLE table_name
ADD CONSTRAINT fk_name
FOREIGN KEY (column_name)
REFERENCES parent_table (parent_column_name);

Удаление поля

ALTER TABLE table_name DROP COLUMN column_name;

Удаление таблицы

Тут надо быть аккуратным, если на эту таблицу есть ссылки из других таблиц, то запрос выдаст ошибку

DROP TABLE table_name;

Задание

Добавить в тестовый проект (Продукты и материалы) систему миграции и какой-нибудь скрипт, меняющий структуру базы данных