EF5 RC CF — Механизм миграции

Entity Framework 5 Release Candidate Code First: Механизм миграции

Сложность 200.

Видимо настал тот момент, когда можно обратиться к Entity Framework, тем более что поступающая информация все сильнее и сильнее радует меня и EF обретает любопытные черты и свойства. Пятая версия фреймворка, в моих глазах уже может претендовать на то, чтобы появиться в боевой среде.

Более всего меня увлекает на данный момент подход CodeFirst, с возможностью миграций, т.е. поддержкой версионности базы. Очевидно на многих, достаточно простых сценариях, миграции отрабатывают хорошо, генерируя скрипты миграции вверх и вниз. Что весьма и весьма полезно. Однако нашлась ложка дегтя в этой бочке меда, либо я чего-то специфического не знаю, о чем все молчат =). Но обо всем по порядку.

Подготовка

Нам понадобится база данных MS SQL. В моем случае это SQL Server 2012, вы можете поставить себе Express версию, хотя скорее всего она уже есть у вас. Далее понадобится .net framework не ниже 4 версии. Конечно же Visual Studio и менеджер пакетов NuGet.

Начнем с создания простого консольного приложения, так как нам будет важен сам факт сохранения и чтения данных.

Вводная информация по EF

Немного информации о том, как работать при подходе Code First, если кто-то только что услышал об этом. Исходя из самого названия и полученной ранее информации вы можете догадаться, что база данных строится исходя из кода приложения, тех классов что мы укажем. Минимальный набор классов для работы это: DbContext и DbSet. Первый класс будет родительским для определения контекста базы, второй будет представлять коллекции, которые отображаются на таблицы базы. Может немного путанно сейчас, но на практике будет понятнее. Сейчас мы к ней перейдем.

Итак, у нас есть пустое приложение и необходимо добавить ссылку на последнюю экспериментальную версию EF5, для чего мы воспользуемся NuGet:

PS > Install-Package EntityFramework –Pre

Главное не забыть параметр Pre! Через некоторое время скачаются все необходимые библиотеки, добавятся ссылки и можно будет начать использовать EF.

Создадим подопытный класс, традиционно это у нас будет Customer.

Тут требуются некоторые пояснения. Начиная с пятой версии EF может использовать в качестве первичного ключа Guid, а первичным ключом по умолчанию считается свойство с именем Id. Если хочется задать другое имя, то на помощь придет атрибут KeyAttribute.

Для работы по восстановлению данных для EF требуется конструктор без параметров, иначе магии не получится. За нас EF не проставит значение первичного ключа, так что мы его инициализируем в конструкторе. Но это и к лучшему.

Следующим шагом будет создание контекста базы. Для этого потребуется создать класс и наследовать его от DbContext. Далее можно будет воспользоваться конструкторами базового класса для детального определения строки соединения. Воспользуемся непривычным для меня приемом и определим строку соединения в файле конфигурации, раз уж он все равно добавился в проект.

Далее можно определить контекст базы данных для работы:

Собственно это и есть все определение контекста, по которому будет создана база. Осталось только задействовать все это вместе. Например, следующим образом:

Последняя строчка только для развлекательных целей, чтобы убедиться, что база создалась. Хотя в этом можно будет убедиться подключившись к ней, и посмотрев, что за данные пришли в базу. Итак, запускаем программу. Все отработало хорошо, и можно обратиться к SSMS.

Вот что можно наблюдать в SSMS:

Тут информация с небольшим забеганием вперед, ну да ничего страшного, сейчас все и расскажу. Как можно видеть в Object Explorer есть база Migration, верьте мне, что она создалась, а не была тут с начала веков. В этой базе есть наша таблица Customers, в которой содержатся указанные нами данные. Так же интересной таблицей для нас будет __MigrationHistory, в которой содержится имя версии базы, слепок модели, время создания, а так же версия EF. Данная таблица присутствует в базе несмотря на то, что мы еще не включали механизм миграций, который сам по себе не включится. Но дальше будет видно, что все наши изменения базы отображаются в этой таблице и можно всегда точно и с уверенностью сказать в каком состоянии находится схема данных базы.

Для закрепления материала, посмотрим, что чтение данных так же не вызывает никаких проблем:

Миграции

Итак, у нас есть программа, будем думать что она большая будет когда-то, а сейчас был первый релиз и на подходе следующая версия в которой мы добавим к классу Customer новое поле. Но прежде чем добавлять поле включим возможность миграций. Для этого потребуется в Package Manager Console набрать:

PS > Enable-Migrations

После чего получим такой вот класс:

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

Создание кода миграции

Итак, миграции включены. Можно править классы.

После этого в консоли NuGet надо будет запустить команду Add-Migration ‘ИмяМиграции’:

PM> Add-Migration IndexAdded

Результатом команды будет новый класс миграции IndexAdded:

Конечно это достаточно легкий случай и по этому судить нельзя, однако можно посмотреть, какие еще служебные методы предлагает нам базовый класс DbMigration

В общем много самых разных, даже переименование колонок. Для выполнения произвольных SQL скриптов существует метод Sql(). К нему мы еще вернемся.

Итак, у нас есть код для обновления, теперь осталось его запустить. Делается это с помощью команды Update-Database.

PS > Update-Database

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

Видим, что теперь в истории базы две записи, а в таблице Customers появилась новая колонка. Замечательно!

Попробуем теперь сделать переименование колонки, обычно во всяких системах автоматической миграции и генерации скриптов это происходит через drop\create, что приведет к потере данных, чего мы, конечно же, не хотим. Но посмотрим, что тут у нас получится. Сменим свойство Name на Title.

Создаем новый скрипт миграции с помощью уже известной нам команды:

PS > Add-Migration NameToTitle

Получаем снова новый класс с указанным нами именем и видим, что код создан по принципу drop\create, но мы можем все исправить!

Мы знаем что это простое переименование и можем воспользоваться методом RenameColumn() или же написать произвольный SQL код. Давайте попробуем второй вариант.

Выглядит убедительно на данный момент, попробуем обновить базу и прочитать данные после таких изменений.

PS > Update-Database

Пробуем запустить приложение и получить данные. Все успешно запустилось.

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

Миграция к предыдущим версиям

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

PS > Update-Database -TargetMigration:IndexAdded

Все должно пройти без ошибок, и посмотрим что там с базой стало:

Все вернулось в нужному виду. Вернув код класса Customer к предыдущему виду, все снова заработало.

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

Косяки

UPD: описанную ниже проблему удалось разрешить удачно. Однако я надеюсь что в релизе описанные сценарий не будет приводить к шаманским пляскам. Решение опишу в следующем посте.

Теперь можно рассмотреть действия, при которых EF отказывается воспринимать изменения, что удивительно на мой взгляд.

Итак, приведем базу к последней версии и не меняя класс Customer (в смысле вернуть его к виду где есть Title, но новых свойств нет), создадим новый класс миграции, который назовем AddCreateStamp. Т.е.

PS > Update-Database
PS > Add-Migration AddCreateStamp

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

Попробуем добавить новую колонку в таблицу и новое поле в класс Customer.

Отлично, все более чем похоже на правду и пробуем обновить базу данных.

PM> Update-Database

Появилось такое предупреждение, но база при этом обновилась.

Но при этом слепок модели не обновился, в чем можно убедиться, выполнив нехитрый скрипт

Три строки всего, и как результат мы не сможем прочитать данные из базы, так как по мнению EF модель и база не совпадают.

И сколько бы мы не обновляли базу, ничего не поможет.

Ок, если почитать внимательно предыдущее сообщение о обновлении, то нам рекомендовали включить автоматические обновления. Попробуем.

PM> Update-Database

Вот такая оказия.

Как это победить я пока что не знаю, но явно должен быть способ.

С одной стороны можно принять идею что «не делайте так», с другой стороны любая миграция должна учитывать изменения и генерировать новый слепок модели. Если кто знает как это сделать – дайте знать, очень интересно!

В остальном весьма приятное впечатление от Code First и механизма миграций.

Итого

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

И да, надо бы покопаться в кишках EF, поискать побольше инфо о внутреннем устройстве, дабы понимать, откуда растут указанные ограничения и каковы возможности.

 

Hard’n’heavy!

 

 

3 комментарий на “EF5 RC CF — Механизм миграции

  1. Я че т не понял в чем криминал с колонкой CreateDate… Почему не пркоанало? И как именно делать нельзя?

    • Да, в статье «EF5 RF CF — Force Recovery Mode» есть детальный ответ и рассмотрение случая.

  2. Вопрос можно?
    Насколько я понял наряду с DropCreateDatabaseIfModelChanges, DropCreateDatabaseAlways и CreateDatabaseIfNotExists появился еще один класс MigrateDatabaseToLatestVersion.

    Если мой Initializer будет наследником этого класса будет ли при первом запуске приложения создана пустая БД?

Оставить комментарий