Январь 2016

Том 31, номер 1

Доступ к данным - EF7-миграции: не новые, но определенно улучшенные

Джули Лерман | Январь 2016

Julie LermanCode First Migrations подвергся некоторым кардинальным изменениям в Entity Framework 7. Хотя основные концепции и этапы миграции остались теми же, рабочий процесс стал гораздо четче и более простым, и теперь он нормально работает с системами управления версиями.

В EF7 две разновидности миграций: привычное разнообразие, используемое вами с NuGet и в большинстве .NET-проектов, и те миграции, которые выполняются как часть DNX Runtime в случае приложений ASP.NET 5.

Я уделила этому внимание в одном из своих учебных курсов Pluralsight (bit.ly/1MThS4J). Хотя некоторые вещи изменились между бета-версией 4, которую я использовала в том курсе, и Release Candidate 1 (RC1), используемым сегодня, мой курс остается отличным ресурсом для того, чтобы увидеть миграции в действии и изучить некоторые из отличий.

Но с выпуском RC1, который считается функционально законченным для предстоящего RTM (запуска в производство), настала пора еще раз подробно рассмотреть EF7-миграции. Я начну с привычных команд Windows PowerShell в Package Manger Console из Visual Studio. Затем мы обсудим некоторые команды, которые следует применять с DNX и ASP.NET 5. В этой статье предполагается, что вы в какой-то мере знаете о средствах инициализации баз данных и миграции в EF6 или более ранних версиях.

Больше никакой магической инициализации баз данных: используем миграции

Поддержка всей этой «магии» обходится дорого, она ограниченна и делает много лишнего. В случае DbInitializer возникает основательная путаница. Поэтому EF DbInitializer наряду с поведением CreateDatabaseIfNotExists по умолчанию теперь удалены. Вместо этого имеется логика, позволяющая явным образом создавать и удалять базы данных (EnsureDeleted, EnsureCreated), которые я с удовольствием задействую в интеграционных тестах.

EF7 по своей природе полагается на миграции. Именно они должны быть теперь вашим рабочим процессом по умолчанию.

Команда enable-migrations существовала потому, что миграции были присоединены к EF и их использование требовало создания какой-никакой инфраструктуры. Та команда не устанавливала никакие новые API; она просто добавляла папку и новый класс DbMigrationConfiguration в ваш проект. С появлением EF7 релевантная логика работает на внутреннем уровне, и вам больше незачем явно сообщать EF о том, что вы хотите задействовать миграции.

Помните, что EF7 способна к композиции. Не всем нужны миграции, поэтому, если они требуются вам, вы должны сами выбрать команды миграции, установив пакет, где они содержатся:

install-package entityframework.commands -pre

Команды миграции в EF7

Командлет Get-Help из Windows PowerShell отображает текущие (на момент выпуска RC1) команды миграции (рис. 1). Этот список должен быть тем же и в RTM-версии.

Команды миграции, перечисляемые NuGet Package Manager с помощью Get-Command
Рис. 1. Команды миграции, перечисляемые NuGet Package Manager с помощью Get-Command

Также заметьте, что в Update-Database больше нет параметра –script. Она делает лишь то, что предполагает ее название: обновляет базу данных. Как всегда, применение этой команды к производственным базам данных не рекомендуется. На рис. 2 показаны подробности о команде Update-Database.

Синтаксис и параметры Update-Database
Рис. 2. Синтаксис и параметры Update-Database

Чтобы создать скрипты, можно использовать новую команду Script-Migration. И больше нет секретного рецепта для создания идемпотентных скриптов (введенных в EF6). Как видно на рис. 3, Idempotent — это параметр.

Параметры Script-Migration
Рис. 3. Параметры Script-Migration

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

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

Чтобы миграции определяли, что нужно делать, должна существовать история предыдущих миграций. Всякий раз, когда вы запускаете команду add-migration, API будет анализировать хронологическую информацию. Ранее миграции сохраняли кодированную версию снимка модели для каждой миграции в базе данных, в таблице _MigrationHistory. Именно ее EF использовал, чтобы выяснить, что должна выполнить миграция. При каждом запуске add-migration подразумевалось, что она обращается к базе данных для чтения этой истории, и это было источником уймы проблем.

В EF7 при создании миграции (через add-migration) в дополнение к знакомому файлу миграции в папке Migrations также создается файл, где хранится текущий снимок модели. Модель описывается по синтаксису Fluent API, применяемому для конфигураций. Если вы вносите изменение в свою модель, а затем добавляете миграцию, снимок модифицируется текущим описанием полной модели. На рис. 4 показан файл миграции, в который введена новая строка, называемая «MyProperty».

Рис. 4. Полный класс миграции newStringInSamurai

public partial class newStringInSamurai : Migration
  {
    protected override void Up(
      MigrationBuilder migrationBuilder)
    {
      migrationBuilder.AddColumn<string>(
        name: "MyProperty",
        table: "Samurai",
        nullable: true);
    }

    protected override void Down(
      MigrationBuilder migrationBuilder)
    {
      migrationBuilder.DropColumn(
        name: "MyProperty", table: "Samurai");
    }
  }

И вот фрагмент обновленного класса ModelSnapshot, где «MyProperty» теперь является частью описания класса Samurai:

modelBuilder.Entity("EF7Samurai.Domain.Samurai", b =>
{
  b.Property<int>("Id")
  .ValueGeneratedOnAdd();
  b.Property<string>("MyProperty");
  b.Property<string>("Name");
  b.HasKey("Id");
});

Итак, индивидуальные миграции описывают, что изменилось, как, впрочем, и раньше. Но теперь в проекте у вас всегда есть описание полной модели. Это означает, что, когда добавляется новая миграция, EF не требуется обращаться к базе данных для получения предыдущей модели. То, что нужно этой инфраструктуре, находится прямо «под рукой». Еще важнее, что миграции гораздо лучше работают с системой контроля версий, поскольку все необходимое присутствует непосредственно в вашем коде и эта система может оперировать изменениями, как в случае любого другого файла. Миграции не скрыты в базе данных, которая может быть недоступна даже членам вашей группы. До EF7 хорошего способа управления миграциями в системе контроля версий не было. Руководство по миграциям для версий до EF7 см. в «Code First Migrations in Team Environments» (bit.ly/1OVO3qr). Оно начинается с совета: «Прихватите чашечку кофе — вам придется прочесть эту статью целиком».

В EF7 таблица истории миграций в базе данных (рис. 5) теперь хранит только идентификатор миграции и версию EF, создавшего ее. И вновь это крупное изменение по сравнению с предыдущими версиями, в которых сам снимок хранился в этой таблице. Update-Database проверит эту таблицу, чтобы выяснить, какие миграции применялись, и применит любые недостающие миграции — даже те, которые не являются следующими друг за другом. Если эта операция должна затрагивать базу данных, я не вижу никаких проблем с миграциями, выполняющими быструю предварительную проверку базы данных в этом сценарии.

Выбор dbo_EFMigrationsHistory отображает миграции, добавленные в таблицу
Рис. 5. Выбор dbo_EFMigrationsHistory отображает миграции, добавленные в таблицу

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

Из-за снимка модели, который EF7 хранит в проекте, нельзя просто удалить файл миграции из проекта, так как снимок станет тогда некорректным. Поэтому наряду с изменением в рабочем процессе миграций появилась новая команда: remove-migration. Она предназначена для отмены миграции, которую вы скорее всего только что добавили, а затем передумали до того, как она была применена к базе данных. Эта команда делает в вашем проекте две вещи: удаляет самый новый файл миграции и обновляет файл снимка в папке Migrations. Любопытно, что это не вызывает переформирования снимка на основе модели. Вместо этого команда модифицирует снимок на основе снимка модели, находящегося в файле Designer.cs, который создается вместе с каждым снимком. Я опробовала это и увидела, что, если я не изменяю модель (т. е. не удаляю нежелательное свойство из класса Samurai), снимок корректируется в соответствии с миграциями. Но не забудьте скорректировать и свою модель. Иначе модель и снимок окажутся несинхронизированными, и скорее всего схема вашей базы данных тоже будет не согласованной с моделью.

В качестве отступления замечу, что Брайс Лэмсон (Brice Lambson), член группы EF, отвечающий за проработку миграций, поделился со мной одним фокусом: если все же удалить вручную файл миграции, при следующем вызове remove-migration обнаружит это и исправит снимок без удаления другого файла миграции.

Подобно add-migration команда remove-migration не влияет на базу данных. Однако она проверяет в файле истории миграций базы данных, применялась ли уже данная миграция. Если да, использовать remove-migration не удастся, по крайней мере пока.

Звучит путанно, знаю. Но мне все равно нравится remove-migration, поскольку альтернатива — просто продолжать дальше и добавлять миграцию всякий раз, когда вы меняете свое решение. Это потенциально может запутать других членов группы или вас спустя какое-то время. Но это невысокая плата за возможность участия миграций в системе контроля версий.

Чтобы прояснить некоторые вещи, в табл. 1 дано руководство по раскрутке (unwinding) нежелательной миграции. Здесь предполагается, что Migration1 применена к базе данных, а Migration2 создана, но никогда к базе данных не применялась.

Табл. 1. Обработка нежелательной миграции

Состояние базы данных
Update-Database не выполнялась после Migration2
Update-Database выполнялась после Migration2

Если вам нужно отменить несколько миграций, начните с вызова update-database, за которой следует имя последней рабочей миграции (last good migration). Затем вы могли бы вызвать remove-migration для отката миграций по одной единовременно и снимка.

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

Обратный инжиниринг с помощью Scaffold-DbContext

Предыдущие версии EF предоставляли возможность обратного инжиниринга базы данных в модели Code First. Microsoft сделала эту возможность доступной через Entity Framework Power Tools и, начиная с EF6, через EF Designer. Также существуют сторонние инструменты, такие как популярное (бесплатное) расширение, EntityFramework Reverse POCO Generator (reversepoco.com). Поскольку в EF7 нет дизайнера, для выполнения этой работы была создана команда scaffold.

Вот параметры для Scaffold-DbContext:

Scaffold-DbContext
  [-Connection] <String> [-Provider] <String>
  [-OutputDirectory <String>] [-ContextClassName <String>]
  [-Tables <String>] [-DataAnnotations] [-Project <String>]
  [<CommonParameters>]

Я большая любительница конфигураций Fluent API, поэтому рада, что они снова имеются по умолчанию и нужно использовать аннотации данных вместо флага DataAnnotations. Ниже показана команда для обратного инжиниринга существующей базы данных с применением этого параметра (я могла бы также выбрать, какие таблицы следует подвергнуть скаффолдингу, но не стану возиться с этим здесь):

Scaffold-DbContext
   -Connection "Server = (localdb)\mssqllocaldb;
     Database=EF7Samurai;"
   -Provider entityframework.sqlserver
   -OutputDirectory "testRE"

Как видно на рис. 6, это приводит к созданию новой папки и нескольких файлов, добавляемых в мой проект. Мне еще предстоит проанализировать все эти классы, поскольку это важная функциональность, но это я оставлю на будущее.

Результат выполнения Scaffold-DbContext
Рис. 6. Результат выполнения Scaffold-DbContext

DNX-версии команд миграции

Команды, выполняемые вами в Package Manager Console, относятся к командам Windows PowerShell, а Windows PowerShell, естественно, является частью Windows. Одна из важнейших особенностей ASP.NET 5 (и EF7) в том, что они больше не зависят от Windows. DNX — это облегченная исполняющая среда .NET, которую можно запускать в OS X и Linux, а также в Windows. Подробнее см. по ссылкеbit.ly/1Gu34wX. Пакет entityframework.commands содержит, в том числе, команды миграции для DNX-среды. Кратко обсудим эти команды.

Вы можете выполнять команды непосредственно в Package Manager Console, если используете Visual Studio 2015, как это делаю я. Иначе вы выполняете их в командной строке релевантной ОС. Посмотрите видеоролик Нейта Макмастера (Nate McMaster) на Channel 9, где он рассказывает о кодировании EF7 и миграций на Mac (bit.ly/1OSUCdo).

Прежде всего убедитесь, что вы находитесь в правильной папке — той, где находится модель из вашего проекта. Раскрывающийся список Default project не относится к вашему файловому пути. Используйте команду dir, чтобы понять, где вы находитесь, а затем просто вводите cd для перехода в нужную папку. Здесь вам поможет завершение по нажатию клавиши Tab. На рис. 7 видно, что, хотя проект по умолчанию размещен в src\EF7WebAPI, для выполнения команд dnx в этом каталоге я должна явным образом сменить этот каталог на путь для этого проекта, по которому хранится моя EF7-модель.

Получение правильного каталога до выполнения DNX-команд
Рис. 7. Получение правильного каталога до выполнения DNX-команд

Перейдя в нужный каталог, можно выполнять команды. Кстати, каждую команду нужно предварять префиксом dnx, а для перечисления всех доступных команд можно использовать dnx ef. Заметьте, что ef — это сокращение, заданное мной в project.json. Подробнее о настройках см. мой видеоролик «Looking Ahead to Entity Framework 7» на Pluralsight (bit.ly/PS_EF7Alpha).

Основными командами являются database, dbcontext и migrations, причем последняя имеет следующие подкоманды:

add     добавляет новую миграцию
list    выводит список миграций
remove  удаляет последнюю миграцию
script  генерирует SQL-скрипт на основе миграций

У каждой команды есть параметры, узнать о которых вы можете, добавив --help, например:

dnx ef migrations add --help

Скажем, если имя миграции — initial, то команда выглядит так:

dnx migrations add initial

Вспомните, что это те же команды, которые вы видели ранее в этой статье, так что вы обнаружите параметры, позволяющие указать проект, DbContext и прочие детали. Команда script имеет идемпотентный параметр, как уже описывалось. Но одно отличие в команде dnx script заключается в том, что по умолчанию скрипт просто перечисляется в командном окне. Вы должны явно задать выходной параметр <file>, чтобы поместить скрипт в файл.

Следующая команда применит миграции к базе данных: 

dnx ef database update

Команда dbcontext имеет две подкоманды: dbcontext list перечислит все DbContext, которые обнаруживаются в проекте. Она здесь потому, что добиться раскрытия вариантов и их выбора по Tab не так просто, как при использовании команд в Windows PowerShell. Наконец, scaffold — это dnx-версия команды DbContext-Scaffold, которую мы уже изучили.

Гораздо более удобная работа с миграциями в EF7

Удаление автоматических миграций в EF7 упростило работу с миграциями. Хотя базовые концепции остались прежними, рабочий процесс стал гораздо более ясным. Снятие барьеров в использовании миграций в рабочей среде групп был крайне важным шагом. Мне нравится способ, которым этого удалось добиться, — переносом снимка модели в исходный код. А поддержка обратного инжиниринга командой scaffold — отличный способ предоставлять этот важный функционал без дизайнера.

В «Design Meeting Notes» за 23 июля от группы EF (bit.ly/1S813ro) включена хорошая табличка со всеми командами Windows PowerShell и dnx после ужесточения схемы именования. Я ожидаю, что в конечном счете появится еще более ясная версия документации, но и текущая вполне полезна.

Я писала эту статью, используя RC1-версию EF7, но эта версия считается функционально законченной, так что скорее всего информация в этой статье не устареет с финальным выпуском EF7 наряду с ASP.NET 5 в начале 2016 г.


Джули Лерман (Julie Lerman) — Microsoft MVP, преподаватель и консультант по .NET, живет в Вермонте. Часто выступает на конференциях по всему миру и в группах пользователей по тематике, связанной с доступом к данным и другими технологиями Microsoft .NET. Ведет блог thedatafarm.com/blog и является автором серии книг «Programming Entity Framework» (O’Reilly Media, 2010), в том числе «Code First Edition» (2011) и «DbContext Edition» (2012), также выпущенных издательством O’Reilly Media. Вы можете читать ее заметки в twitter.com @julielerman и смотреть ее видеокурсы для Pluralsight на juliel.me/PS-Videos.

Выражаю благодарность за рецензирование статьи эксперту Брайсу Лэмсону (Brice Lambson).