Миграции модели данных

125

В примерах ранее мы использовали автоматическое соглашение по инициализации базы данных при изменении модели. В предыдущей статье мы показали, как можно изменить способ инициализации базы данных. Но при любой настройке инициализатора, когда модель данных изменилась, вам в любом случае нужно удалить базу данных, чтобы воссоздать ее снова. Очевидно что такое поведение может вас не устроить, т.к. при удалении базы данных удаляются и все данные из нее.

Изменить такое поведение можно с использованием миграций, которые позволяют хранить различные версии модели в таблице __MigrationHistory. Как мы говорили ранее, для отслеживания состояния модели данных Entity Framework использует автоматически генерируемую таблицу __MigrationHistory, в которой хранится сериализованная в двоичный формат модель данных. (Также, как говорилось в статье “Использование Code-First” вы можете использовать подход Code-Second, при котором такая проблема вообще не возникает.)

Для управления миграциями модели в Code-First используется класс DbMigration из пространства имен System.Data.Entity.Migrations. Миграции позволяют указать текущую версию модели, показав какие должны быть внесены изменения в базу данных. Допустим у вас есть следующая модель данных:

public class Customer
{
    public int CustomerId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int? Age { get; set; }

    [Column(TypeName = "image")]
    public byte[] Photo { get; set; }

    // Ссылка на заказы
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int OrderId { get; set; }
    public string ProductName { get; set; }
    public string Description { get; set; }
    public int Quantity { get; set; }
    public DateTime PurchaseDate { get; set; }

    // Ссылка на покупателя
    public Customer Customer { get; set; }
}

Далее вы какое-то время работаете с приложением и возможно вставляете данные в таблицы Customers и Orders. В какой-то момент вы захотели ввести ограничение на длину для поля FirstName в классе Customer и удалить столбец LastName. Напомню, что для этого можно использовать атрибут MaxLength в классе модели:

public class Customer
{
    public int CustomerId { get; set; }

    [MaxLength(20)]
    public string FirstName { get; set; }
    // public string LastName { get; set; }
    
    // ...
}

Если вы теперь запустите приложение, то Code-First обнаружит, что модель изменилась и, в зависимости от типа используемого инициализатора базы данных, либо выдаст исключение, либо попытается удалить и воссоздать базу данных с новой структурой. В любом случае, вы потеряете все данные из этих таблиц. Т.к. в нашей новой модели мы критически не затронули структуру модели, а просто добавили ограничение и удалили ненужный столбец, то мы можем использовать миграцию модели, чтобы указать Code-First, что можно просто изменить базу данных без ее удаления.

Поддержка миграций в Entity Framework реализована с помощью пакета EntityFramework.Migrations, который устанавливается автоматически, при установке Entity Framework из диспетчера NuGet. Чтобы включить миграции, нужно выполнить команду Enable-Migrations в консоли диспетчера NuGet (открыть консоль в Visual Studio можно с помощью команды меню Tools --> Library Package Manager --> Package manager Console):

Enable-Migrations -ProjectName "CodeFirst" -StartUpProjectName "ProfessorWeb.EntityFramework" 

Указание директив ProjectName и StartUpProjectName не обязательно. Напомню, что в нашем тестовом проекте модель данных определяется в проекте классов CodeFirst, а само приложение ASP.NET, где мы работаем с данными, называется ProfessorWeb.EntityFramework, поэтому в этой команде я явно задал имена проектов. Если вы используете модель Code-First в том же проекте, где работаете с данными, указывать эти директивы необязательно, достаточно просто выполнить команду Enable-Migrations.

После запуска этой команды, в проекте CodeFirst будет добавлена новая папка Migrations, содержащая два файла:

Добавление папки Migrations в проект

В файле Configuration.cs настраивается конфигурации миграций. Во втором файле InitialCreate.cs указывается структура первоначальной модели. Далее вы можете использовать в консоли две команды, для управления миграциями:

Add-Migration

Создает шаблон для следующей миграции на основании изменений, произведенных в модели с момента создания последней миграции.

Update-Database

Обновляет базу данных с использованием последних миграций.

Давайте добавим новую миграцию, используя команду Add-Migration SampleMigrations. Если вы используете структуру решения как у меня, то в этой команде также нужно будет явно указать имя проекта CodeFirst, к которому будет добавлена новая миграция:

Add-Migration SampleMigrations -ProjectName "CodeFirst"

В результате Visual Studio добавит новый файл миграции в папку Migrations, имеющий в имени строку SampleMigration.cs. Откройте этот файл. Если вы изменили модель, как было показано в пример выше, то вы увидите следующий код:

namespace CodeFirst.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class SampleMigrations : DbMigration
    {
        public override void Up()
        {
            AlterColumn("dbo.Customers", "FirstName", c => c.String(maxLength: 20));
            DropColumn("dbo.Customers", "LastName");
        }
        
        public override void Down()
        {
            AddColumn("dbo.Customers", "LastName", c => c.String());
            AlterColumn("dbo.Customers", "FirstName", c => c.String());
        }
    }
}

Здесь, в методе Up() указывается то, как должна выглядеть модель после изменения. Мы используем различные вспомогательные методы класса DbMigration, чтобы указать ограничение на длину поля FirstName (метод AlterColumn()) и удалить столбец LastName (метод DropColumn()).

В этом классе обязательно нужно реализовать метод Down(), в котором указывается то, как модель выглядела до изменений. Это нужно для того, чтобы Code-First понимал, к какой структуре модели можно применять миграцию, описанную в методе Up(). До изменения модели у нас не было ограничения на поле FirstName, поэтому мы вызываем метод AlterColumn() и не передаем никаких параметров в третьем аргументе этого метода, вызвав String() – тем самым указав, что для строкового поля не было никаких ограничений по длине. Также, до изменения модели у нас существовал столбец LastName, поэтому его нужно добавить в методе Down().

Теперь вы можете обновить базу данных на основе данной миграции, использовав команду Update-Database:

Update-Database -ProjectName "CodeFirst"

После запуска этой команды, пакет Code First Migrations сравнит список миграций в папке Migrations, выберет ту, которая соответствует текущей модели данных и обновит структуру базы без удаления записей из таблиц:

Структура базы данных после обновления из миграций модели

Если в таблице Customers содержались какие-нибудь данные, то они будут сохранены после обновления базы данных.

Пройди тесты
Лучший чат для C# программистов