Миграция данных в Entity Framework Code First: August 2011 CTP

Команда разработчиков Entity Framework выпустила инструмент для миграции данных в базах, созданных с использованием Code First. Необходимо отметить, что это только предварительная версия. В ней не реализованы некоторые функции и могут быть ошибки. Но все же рассмотрим представленное решение подробнее.

Установка

Стоит отметить, что использовать Code First Migrations можно в любых проектах, где задействован Entity Framework. В данном случае, для демонстрации создадим пустой ASP.NET MVC 3 проект EFMigrationsDemo.

Перейдем в NuGet Manager Console и введем команду для установки поддержки миграции баз данных:

PM> Install-Package EntityFramework.SqlMigrations

Разумеется, это можно сделать и через графический интерфейс, выбрав установочный пакет EntityFramework.SqlMigrations. Обратите внимание, что он зависит от библиотеки Entity Framework и, в случае необходимости, обновит её в текущем проекте.

Демонстрационный проект

Разработку демонстрационного проекта начнем с Модели. Предположим, это будет информация о пользователе. Тогда в папке Models создадим класс UserProfile:

namespace EFMigrationsDemo.Models
{
    public class UserProfile
    {
        public int Id { get; set; }

        public string Login { get; set; }

        public string Email { get; set; }

        public string Password { get; set; }
    }
}

Создадим сборку демонстрационного проекта, скомпилировав его. Затем воспользуемся заготовками в диалоге Add Controller для создания Контроллера и стандартных Представлений. При этом укажем в качестве Модели класс UserProfile и задействуем Entity Framework для хранения данных. Чтобы не настраивать маршруты, используем имя Контроллера по умолчанию - HomeController.

К сожалению, в текущей версии Code First Migrations нельзя указать провайдера данных. Поэтому его можно использовать только с Microsoft SQL Server / SQL Express / SQL Azure. А значит, необходимо добавить соответствующую им строку соединения в файл web.config:

<configuration>
.........

  <connectionStrings>
    <add name="EFMigrationsDemoContext"
         connectionString="Data Source=(local); 
                           Initial Catalog=EFMigrationsDemo; Integrated Security=true;"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

.........
</configuration>

Проект готов. Запустим его и добавим информацию о нескольких произвольных пользователях чтобы заполнить базу данных. В дальнейшем модифицируем Модель и проследим за сохранностью уже введенных данных.

Изменяем Модель

В результате работы демонстрационного веб-приложения, Entity Framework Code First создаст таблицу UserProfiles со следующими полями:

[Id] [int] IDENTITY(1,1) NOT NULL,
[Login] [nvarchar](max) NULL,
[Email] [nvarchar](max) NULL,
[Password] [nvarchar](max) NULL

Давайте теперь изменим Модель. Например, добавим свойство Description для хранения описания пользователя:

namespace EFMigrationsDemo.Models
{
    public class UserProfile
    {
        public int Id { get; set; }

        public string Login { get; set; }

        public string Email { get; set; }

        public string Password { get; set; }

        public string Description { get; set; }
    }
}

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

Однако сейчас, вместо этого перейдем в NuGet Manager Console и введем команду для миграции update-database. В результате будет ��тображено следующее:

PM> update-database
No pending custom scripts found.
Ensuring database matches current model.
 - Performing automatic upgrade of database.
  - Starting rebuilding table [dbo].[EdmMetadata]...
  - Caution: Changing any part of an object name could break scripts and stored procedures.
  - Starting rebuilding table [dbo].[UserProfiles]...
  - Caution: Changing any part of an object name could break scripts and stored procedures.
  - Update complete.

Схема базы данных обновлена. Обратите внимание, что и метаданные Code First (EdmMetadata) в ней также заменены на соответствующие новой Модели.

Давайте теперь посмотрим на произошедшие изменения. В первую очередь, модифицирована сама таблица UserProfiles, которая теперь имеет следующий вид:

[Id] [int] IDENTITY(1,1) NOT NULL,
[Login] [nvarchar](max) NULL,
[Email] [nvarchar](max) NULL,
[Password] [nvarchar](max) NULL,
[Description] [nvarchar](max) NULL

Кроме того, в проекте появилась папка Migrations, в которой расположены данные о проделанных операция по модернизации таблиц базы данных.

Разработчику остается только исправить Представления соответствующим образом. В данном случае – добавить поля для ввода и отображения нового свойства Description.

Запустим демонстрационное веб-приложение для того, чтобы убедиться в его работоспособности.

Попробуем внести еще одно изменение в Модель – переименуем свойство Email в PersonalEmail:

namespace EFMigrationsDemo.Models
{
    public class UserProfile
    {
        public int Id { get; set; }

        public string Login { get; set; }

        public string PersonalEmail { get; set; }

        public string Password { get; set; }

        public string Description { get; set; }
    }
}

Вновь запустим процесс миграции базы данных с помощью команды update-database:

PM> update-database 
No pending custom scripts found. 
Ensuring database matches current model. 
 - Performing automatic upgrade of database. 
Update-Database : - .Net SqlClient Data Provider: Msg 50000, Level 16, State 127, Line 6 
Rows were detected. The schema update is terminating because data loss might occur.
At line:1 char:16
+ update-database <<<< 
  + CategoryInfo : NotSpecified: (:) [Update-Database], Exception
  + FullyQualifiedErrorId:UnhandledException,System.Data.Entity.Migrations.Commands.MigrateCommand

Результат – ошибка, которая указывает на то, что может произойти потеря данных.

Дело в том, что Code First Migrations ничего не знает о сути изменений, сделанных разработчиком. Он оперирует только описанием старой и новой Моделей. С этой точки зрения, в данной ситуации в таблицу необходимо добавить одно поле (PersonalEmail) и одно удалить (Email).

Для разрешения сложившийся ситуации необходимо явно указать свои намеренья. В частности, отметить переименование с помощью параметра -Renames:

PM> update-database -Renames:"UserProfile.Email=>UserProfile.PersonalEmail"
No pending custom scripts found.
Ensuring database matches current model.
 - Performing automatic upgrade of database.
  - The following operation was generated from a refactoring log file
  - Rename [dbo].[UserProfiles].[Email] to PersonalEmail
  - Caution: Changing any part of an object name could break scripts and stored procedures.
  - Update complete.

Миграция прошла успешно. Исправим в Представлениях имя соответствующего свойства на PersonalEmail и запустим проект. Он по прежнему полностью работоспособен и содержит введенные ранее данные. Таблица UserProfiles изменилась и выглядит следующим образом:

[Id] [int] IDENTITY(1,1) NOT NULL,
[Login] [nvarchar](max) NULL,
[PersonalEmail] [nvarchar](max) NULL,
[Password] [nvarchar](max) NULL,
[Description] [nvarchar](max) NULL

Теперь самое время рассмотреть какие изменения могут быть внесены в Модель.

Распознаваемые изменения Модели

Согласно записи в блоге ADO.NET, Code First Migrations определяет:

  • Добавление свойства или класса. При этом в соответствующие ячейки таблицы будет записано null или значение по умолчанию (согласно CLR) в зависимости от типа.
  • Переименование свойства или класса. Требуется уточнение через параметр -Renames.
  • Переименование колонки/таблицы в базе данных без изменения имени соответствующего свойства/класса (с использованием Fluent API).
  • Удаление свойства. Требуется подтверждение удаления данных с помощью параметра -Force.

Предварительный просмотр изменений базы данных

Code First Migrations позволяет предварительно оценить предстоящие изменения базы данных. Для этого в команде update-database необходимо указать параметр -Script. Например, в предыдущем примере после переименования свойства Модели можно было бы выполнить команду:

PM> update-database -Script -Renames:"UserProfile.Email=>UserProfile.PersonalEmail"

Тогда, в результате, вместо модификации базы данных, был бы создан файл с SQL кодом для этого:

SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;

SET NUMERIC_ROUNDABORT OFF;

GO

PRINT N'Rename [dbo].[UserProfiles].[Email] to PersonalEmail';

GO

EXECUTE sp_rename @objname = N'[dbo].[UserProfiles].[Email]', @newname = N'PersonalEmail', @objtype = N'COLUMN';

GO

-- Refactoring step to update target server with deployed transaction logs

IF OBJECT_ID(N'dbo.__RefactorLog') IS NULL
BEGIN
    CREATE TABLE [dbo].[__RefactorLog] (OperationKey UNIQUEIDENTIFIER NOT NULL PRIMARY KEY)
    EXEC sp_addextendedproperty N'microsoft_database_tools_support', N'refactoring log', N'schema', N'dbo', N'table', N'__RefactorLog'
END
GO

INSERT INTO [dbo].[__RefactorLog] (OperationKey) values ('256218e0-f552-0f12-0264-5bdeb4175565')

GO

INSERT INTO [dbo].[__ModelSnapshot] ([Migration], [Hash], [Model]) VALUES ('Automatic Migration', 0x51269BFC1572F233EC6B84052A5EC3BE07DD36222141CFEDCBD4911D0B340B3A, 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF54AD7E074A10880601324D8904010ECC188CDE692EC1D69472329AB2A81CA6556655D661640CCED9DBCF7DE7BEFBDF7DE7BEFBDF7BA3B9D4E27F7DFFF3F5C6664016CF6CE4ADAC99E2180AAC81F3F7E7C1F3F22FEC7BFF71F7CFC7BBC5B94E9655E3745B5FCECA3DDF1CE4769BE9C56B36279F1D947EBF67CFBE0A3DFE3E8374E1E9FCE16EFD29F34EDF6D08EDE5C369F7D346FDBD5A3BB779BE93C5F64CD78514CEBAAA9CEDBF1B45ADCCD66D5DDBD9D9D83BBBB3B777302F111C14AD3C7AFD6CBB658E4FC07FD79522DA7F9AA5D67E517D52C2F1BFD9CBE79CD50D317D9226F56D934FFECA3D3675F141775D6121ECDD37C518DE5958FD2E3B2C8089DD77979FE9EB8ED3C046E1FD95EA9DF53C2AFBD7E73BDCAB9EFCF3EFAAAC9EB9775755E94B9DF909AFE5EF975F0017D440D5779DD5EBFCACFF5F5B3D947E9DDF0BDBBDD17ED6BDE3BC0807E5BB6F7F63E4A5FACCB329B94F4C1795636F947E9EAD347AFDBAACE3FCF973991249FBDCCDA36AF977837E71128251EAD3EBD1D311EDEDDD90331EE66CB65D532957B8877D07C5E5D144B83E9EBB626CEF9287D56BCCB67CFF3E5453BB7D87E91BD339FD0AF1FA55F2D0B62347AA9ADD7B93F3AF97B73AF2F8915AB65569E2EB2A2FCE1F79E35CD5555DB09FAA175FC346FA675B19279F959EDFBF15D27031B258334C317799BCDB236FB91640468B266FA76D6CC7F4EA78A946B9B154406335F1D058AEFF3776D67EEE4DDD779DBD77FA46B5D7FA270C7BE7AEC52A50BCA639818289F9FA2C3B4037266E2AED809634FEE0E1894C75F64AB15CD806760F493F4B5589793EDD7EF6F3C1602E3EEB489D8108BADED89F832BBC83BDF52D784E9B3A26EDAA734F249069E38992D7ACDDE73FA4CAF9159ECCAA69B09F3127E8F338D5ADD60DE3BF01C719FD1781724753C741D38A1B6C1A8EAFBAFA75999D511E13FA9CAF56239A44036BDADD6CA07A01FDD1E46C7F6F8B03A5FBD074C6B510270F6D3DB430A4C840F2CF8A20FEFF1DDCE7475F9C3D332DAB2A3B6BB4C772B960CB4C137C19283F68821DEC4921BDFFED96149CF4CF840BC8F7FC893D553B4DD26B677AB703B8AF5B12AB940FBF2F8BBEE7C4FEB49938F5222D2653183C67B7DDDB4F9628C06E3D7BFA83C290B1AAF6BF045B62CCEF3A67D53BDCD119A9052FEFAC180B5F24D332BFF5F1C1114A0C08D1E4ECF2D7A0F4F7E7999D5D379566F2DB27777DED74D8D7AE71F06B1E3717F10B08817FD1EF0FEBFE119FFAC7088A793BE61B275BDD4BE3734604D22D278837B2A1AE6B38F66938A8621E886EEEDED1DD89BFCD7585FDF8483DBD7AF8FEFFA4995C7C4E2C585038114CB329F82E31D50D3E66C795E9989A651FA1899265D3E50FC8FEBB638CFA62D7D3DCD9B86839B9FCCCA3546B998E4B3B3E597EB76B56E8F9B265F4CCA6B7FBC8FEF6EEE9FBDF810E7C75FB2CC7A06E5EB0F81D02C6808F997CB27EBA29C59BC9FF5E56008047854858BB0A2E08EC05D5C5B482FAAE52D0129F99EE6AB7C09D17C932F5625016BBE5CBECE2EF361DC6EA66148B1C74F8B8C7CA6854F41F94431799D51CF5E17D481FF86EB8FFE2476A58CDED1FF130000FFFFADDDAAC946140000);

GO


DELETE [dbo].[EdmMetadata];INSERT INTO [dbo].[EdmMetadata]([ModelHash]) VALUES ('E4DBAB3515D4C9E4180E4034EB9A5B7720C57A69CF3641EEF6E64FF5FB41A405')

Теперь у разработчика появляется два варианта установки изменений: или выполнить команду без параметра -Script или запустить полученный файл на SQL сервере.

Необходимо отметить одно ограничение, которое есть на данный момент: для корректного создания SQL кода необходимо чтобы база данных была хотя бы один раз модифицирована с использованием команды update-database. В противном случае он будет содержать только команды создания таблиц в их текущем виде.

Перенос изменений с компьютера разработчика на рабочий сервер

Рассмотренный выше вариант позволяет переносить изменения на сервер только после тестирования на локальных компьютерах. Здесь пригодиться еще один параметр: -ConnectionString. Он позволяет задать строку соединения с базой данных. Таким образом можно использовать рабочие таблицы и создать для них скрипт миграции на текущую Модель. Например:

update-database -Script –ConnectionString "Data Source=(local); Initial Catalog=EFMigrationsWork; Integrated Security=true;"

Результатом будет файл с SQL командами для модификации базы данных, который уже можно запустить на рабочем сервере после установки новой версии веб-приложения.

Текущие ограничения Code First Migrations August 2011 CTP

Поскольку это только предварительная версия (Alpha), то не все запланированные возможности реализованы. В частности:

  • Как уже было отмечено, нет возможности указать провайдера данных. Поэтому Code First Migrations работают только с SQL Server / SQL Express / SQL Azure.
  • Для работы SQL кода миграции требуется полные права (full trust). Конечно, это не проблема во время разработки, но может создать трудности при переносе на рабочий сервер.
  • Текущая версия доступна только через NuGet.
  • Не реализована отмена соделанных изменений. В дальнейшем планируется добавить создание SQL скрипта Down.sql, с помощью которого можно будет вернуть базу данных в исходное состояние.

Комментарии (4) -

Артур 30.07.2011 1:55:00

Из-за этих хеш-кодов таблиц сами себе проблему создали

Ну так у этих хэш кодов вполне определенная цель - проверка соответствия таблиц и Модели. Без них бы пришлось делать туже самую проверку, но просто по другому.

Александр 22.10.2013 17:36:53

Добрый день, Андрей!
EF 6. Можно включить в модель уже существующую таблицу с данными?
У меня конечно есть 1 вариант:
1. Переименовать целевую таблицу.
2. Добавить в модель описание сущности.
3. Провести миграцию.
4. Удалить созданную таблицу и вернуть название целевой.
Есть способ лучше?

@ Александр: Ну это наверное будет самый простой вариант.

Добавить комментарий