# Системы миграции данных Модель данных в процессе разработки имеет свойство изменяться, и в какой-то момент она перестает соответствовать базе данных. Функция **системы миграции** сводится к тому, чтобы синхронизировать структуру БД с моделью данных в приложении без потери существующих данных. Не помню, затрагивали ли мы этот момент при выборе фреймворка для работы с БД, на всякий случай расскажу еще раз. Существует два варианта синхронизации модели и базы: - __Code First__ - "сначала код", то есть мы описываем модель и структура базы данных меняется в соответствии с моделью (мы этот подход не используем, он применяется в __Entity Framework__) - __DB First__ - "база данных первична", то есть мы сначала меняем структуру базы данных и только потом приводим модель в соответствие с ней. Это как раз наш случай. Для того, чтобы структура базы данных менялась в нужных версиях приложений (раньше в принципе можно, но точно не позже) существуют __системы миграции__, рассмотрим наиболее популярные: >Взято [отсюда](https://habr.com/ru/companies/crosstech/articles/453930/) - 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 ## Добавление возможности миграции в АПИ В десктопном приложении использовать миграции можно, но не нужно - нет гарантии, что не запустятся одновременно две копии программы. Правильнее миграции накатывать либо администратору БД (скрипты мы в любом случае будем писать), либо при запуске новой версии АПИ (этот вариант предпочтительнее, так как исключается человеческий фактор - миграция применится в тот момент, когда она нужна) [Делал по этому туториалу](https://medium.com/@niteshsinghal85/dbup-postgresql-dapper-in-asp-net-core-c3be6c580c54) 1. В проекте с АПИ созадем каталог `migrations`, где будем хранить скрипты для миграции 1. В настройках проекта (файл `*.csproj`) укажем, чтобы все SQL-файлы из этого каталога включались в исполняемый файл ```xml ... ``` 1. Создадим файл для миграции `migrations/agent_type.sql` >Вообще миграции предназначены для изменения структуры БД, но никто не запрещает нам выполнять и DML- скрипты, например, для заполнения словарей ```sql insert into AgentType values (1, "индивидуальный предприниматель"), (2, "Общество с ограниченной ответственностью"); ``` 1. Устанавливаем библиотеку `DBup` Можно через __Nuget__, а можно и в консоли: ```sh dotnet add package dbup-mysql ``` 1. Дописываем в __начало__ нашего `Program.cs` код для накатывания миграций ```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`: ```sql 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 ` задает значение по-умолчанию Рассмотрим DDL скрипт таблицы `Agent` - тут у нас появляются внешние ключи: ```sql 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`)``) ### Изменение полей существующей таблицы #### Добавление поля ```sql ALTER TABLE table_name ADD COLUMN new_column_name data_type [constraints]; ``` #### Переименовывание поля * универсальный вариант, меняющий и название поля и его тип ```sql ALTER TABLE имя_таблицы CHANGE старое_имя новое_имя ТИП_ДАННЫХ; ``` * простой вариант, меняющий только название поля ```sql ALTER TABLE имя_таблицы RENAME COLUMN старое_имя TO новое_имя; ``` #### Удаление поля ```sql ALTER TABLE table_name DROP COLUMN column_name; ``` #### Изменение типа данных или ограничений поля ```sql ALTER TABLE table_name MODIFY COLUMN column_name new_data_type [new_constraints]; ``` #### Добавление внешнего ключа ```sql ALTER TABLE table_name ADD CONSTRAINT fk_name FOREIGN KEY (column_name) REFERENCES parent_table (parent_column_name); ``` #### Удаление поля ```sql ALTER TABLE table_name DROP COLUMN column_name; ``` ### Удаление таблицы >Тут надо быть аккуратным, если на эту таблицу есть ссылки из других таблиц, то запрос выдаст ошибку ```sql DROP TABLE table_name; ``` ## Задание Добавить в тестовый проект (Продукты и материалы) систему миграции и какой-нибудь скрипт, меняющий __структуру__ базы данных