Code First Migrations в командных средах

Примечание.

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

Схватить кофе, вам нужно прочитать всю эту статью

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

Некоторые общие рекомендации

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

Каждый участник группы должен иметь локальную базу данных разработки

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

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

Избегайте автоматической миграции

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

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

Общие сведения о том, как работает миграция

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

Первая миграция

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

First Migration

Текущая модель вычисляется из кода (1). Затем необходимые объекты базы данных вычисляются моделью (2), так как это первая миграция модели отличается просто пустой моделью для сравнения. Необходимые изменения передаются генератору кода для создания требуемого кода миграции (3), который затем добавляется в решение Visual Studio (4).

Помимо фактического кода миграции, хранящегося в основном файле кода, миграции также создают некоторые дополнительные файлы кода. Эти файлы являются метаданными, которые используются миграциями и не являются тем, что следует редактировать. Один из этих файлов — это файл ресурса (RESX), содержащий моментальный снимок модели во время создания миграции. Вы увидите, как это используется на следующем шаге.

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

Последующие миграции

Позже вы вернетесь и внесите некоторые изменения в модель. В нашем примере мы добавим свойство URL-адреса в блог. Затем вы запустите команду, например Add-Migration AddUrl , чтобы создать шаблон миграции, чтобы применить соответствующие изменения базы данных. Ниже приведены шаги высокого уровня, которые выполняет эта команда.

Second Migration

Как и в последний раз, текущая модель вычисляется из кода (1). Однако на этот раз существуют миграции, поэтому предыдущая модель извлекается из последней миграции (2). Эти две модели отличаются для поиска необходимых изменений базы данных (3), а затем процесс завершается, как и раньше.

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

Зачем беспокоиться с моментальным снимком модели?

Возможно, вам интересно, почему EF обекеры с моментальным снимком модели — почему бы не просто посмотреть на базу данных. Если да, читайте дальше. Если вы не заинтересованы, вы можете пропустить этот раздел.

Существует ряд причин, по которым EF сохраняет моментальный снимок модели:

  • Она позволяет базе данных отходить от модели EF. Эти изменения можно внести непосредственно в базу данных или изменить шаблонный код в миграциях, чтобы внести изменения. Вот несколько примеров этого на практике:
    • Вы хотите добавить вставленный и обновленный столбец в одну или несколько таблиц, но не хотите включать эти столбцы в модель EF. Если миграции смотрели на базу данных, она постоянно пытается удалить эти столбцы каждый раз, когда вы создали шаблон миграции. С помощью моментального снимка модели EF будет обнаруживать только допустимые изменения в модели.
    • Вы хотите изменить текст хранимой процедуры, используемой для обновлений, чтобы включить некоторые журналы. Если миграции рассмотрели эту хранимую процедуру из базы данных, она будет постоянно пытаться вернуться к определению, которое ожидает EF. Используя моментальный снимок модели, EF будет изменять хранимую процедуру только при изменении формы процедуры в модели EF.
    • Эти же принципы применяются к добавлению дополнительных индексов, включая дополнительные таблицы в базе данных, сопоставление EF с представлением базы данных, которое находится над таблицей и т. д.
  • Модель EF содержит больше, чем только форму базы данных. Наличие всей модели позволяет миграции просмотреть сведения о свойствах и классах в модели и их сопоставлении со столбцами и таблицами. Эта информация позволяет миграции быть более интеллектуальными в коде, который он формирует. Например, если изменить имя столбца, который свойство сопоставляется с миграциями, может обнаружить переименование, увидев, что это то же свойство, то, что невозможно сделать, если у вас есть только схема базы данных. 

Что приводит к проблемам в средах группы

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

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

Пример конфликт слияния

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

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

Starting Point

Разработчик #1 и разработчик #2 теперь вносит некоторые изменения в модель EF в локальной базе кода. Разработчик #1 добавляет свойство Rating в блог и создает миграцию AddRating , чтобы применить изменения к базе данных. Разработчик #2 добавляет свойство читателей в блог и создает соответствующую миграцию AddReaders . Оба разработчика запускают Update-Database, чтобы применить изменения к локальным базам данных, а затем продолжить разработку приложения.

Примечание.

Миграции префиксируются меткой времени, поэтому наш графический элемент представляет, что миграция AddReaders из Developer #2 происходит после миграции AddRating из Developer #1. Независимо от того, создает ли разработчик #1 или #2 миграцию, не имеет никакой разницы в проблемах, связанных с работой в команде, или процесс объединения их, который мы рассмотрим в следующем разделе.

Local Changes

Это счастливый день для разработчика #1, так как они происходят, чтобы отправить свои изменения в первую очередь. Так как никто другой не проверка, так как они синхронизировали свой репозиторий, они могут просто отправлять свои изменения без каких-либо слияний.

Submit Changes

Теперь пришло время отправить разработчику #2. Они не так повезло. Так как кто-то другой отправил изменения после синхронизации, им потребуется вытащить изменения и объединить их. Система управления версиями, скорее всего, сможет автоматически объединить изменения на уровне кода, так как они очень просты. Состояние локального репозитория Разработчика #2 после синхронизации отображается на следующем рисунке. 

Pull From Source Control

На этом этапе разработчик #2 может запустить Update-Database , который обнаружит новую миграцию AddRating (которая не была применена к базе данных разработчика #2) и применит ее. Теперь столбец "Рейтинг" добавляется в таблицу "Блоги", а база данных синхронизирована с моделью.

Есть несколько проблем, хотя:

  1. Несмотря на то что update-Database будет применять миграцию AddRating, она также вызовет предупреждение: не удается обновить базу данных для сопоставления текущей модели, так как существуют ожидающие изменения и автоматическая миграция отключена... Проблема заключается в том, что моментальный снимок модели, хранящийся в последней миграции (AddReader), отсутствует свойство Rating в блоге (так как оно не было частью модели при создании миграции). Код сначала обнаруживает, что модель в последней миграции не соответствует текущей модели и вызывает предупреждение.
  2. Запуск приложения приведет к ошибке InvalidOperationException с сообщением о том, что модель, поддерживающая контекст BloggingContext, изменилась с момента создания базы данных. Рассмотрите возможность использования code First Migrations для обновления базы данных..." Опять же, проблема заключается в том, что моментальный снимок модели, хранящийся в последней миграции, не соответствует текущей модели.
  3. Наконец, мы ожидали, что запуск надстройки теперь создаст пустую миграцию (так как к базе данных нет изменений). Но поскольку миграции сравнивают текущую модель с одной из последней миграции (которая отсутствует свойство Rating ), она фактически будет создавать шаблон другого вызова AddColumn , чтобы добавить в столбец Rating . Конечно, эта миграция завершится ошибкой во время обновления базы данных , так как столбец "Рейтинг " уже существует.

Разрешение конфликт слияния

Хорошая новость заключается в том, что не слишком трудно справиться с слиянием вручную, если у вас есть представление о том, как работает миграция. Так что если вы пропустили вперед этот раздел... К сожалению, сначала нужно вернуться и прочитать остальную часть статьи!

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

Вариант 1. Добавление пустой миграции "слияние"

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

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

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

  1. Убедитесь, что все ожидающие изменения модели в локальной базе кода были записаны в миграцию. Этот шаг гарантирует, что вы не пропустите никаких законных изменений, когда приходит время создать пустую миграцию.
  2. Синхронизация с системой управления версиями.
  3. Запустите update-Database, чтобы применить любые новые миграции, которые другие разработчики проверка в. Примечание.Если вы не получаете никаких предупреждений из команды Update-Database, то не было новых миграций от других разработчиков и нет необходимости выполнять какие-либо дальнейшие слияния.
  4. Запустите pick_a_name надстройки <—IgnoreChanges (например, слияние надстроек —IgnoreChanges).> Это создает миграцию со всеми метаданными (включая моментальный снимок текущей модели), но будет игнорировать любые изменения, которые он обнаруживает при сравнении текущей модели с моментальным снимком в последних миграциях (это означает, что вы получаете пустой метод Up и Down ).
  5. Запустите update-Database , чтобы повторно применить последнюю миграцию с обновленными метаданными.
  6. Продолжить разработку или отправить в управление версиями (после выполнения модульных тестов).

Ниже приведено состояние базы локального кода Разработчика #2 после использования этого подхода.

Merge Migration

Вариант 2. Обновление моментального снимка модели в последней миграции

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

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

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

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

  1. Убедитесь, что все ожидающие изменения модели в локальной базе кода были записаны в миграцию. Этот шаг гарантирует, что вы не пропустите никаких законных изменений, когда приходит время создать пустую миграцию.
  2. Синхронизация с системой управления версиями.
  3. Запустите update-Database, чтобы применить любые новые миграции, которые другие разработчики проверка в. Примечание.Если вы не получаете никаких предупреждений из команды Update-Database, то не было новых миграций от других разработчиков и нет необходимости выполнять какие-либо дальнейшие слияния.
  4. Запустите update-Database –TargetMigration second_last_migration> (в примере ниже будет update-Database –TargetMigration <AddRating). При этом база данных откатится к состоянию второй последней миграции— фактически "отмена применения" последней миграции из базы данных. Примечание.Этот шаг необходим, чтобы сделать его безопасным для изменения метаданных миграции, так как метаданные также хранятся в __MigrationsHistoryTable базы данных. Поэтому этот параметр следует использовать только в том случае, если последняя миграция находится только в локальной базе кода. Если к другим базам данных применена последняя миграция, их также придется откатить и повторно применить последнюю миграцию для обновления метаданных. 
  5. Запустите full_name_including_timestamp_of_last_migration "Миграция <надстроек"> (в примере, который мы выполнили, это будет примерно так же, как 201311062215252_AddReaders надстройки). Примечание.Необходимо включить метку времени, чтобы миграции знали, что необходимо изменить существующую миграцию, а не создать новую. Это приведет к обновлению метаданных для последней миграции в соответствии с текущей моделью. Вы получите следующее предупреждение, когда команда завершится, но это именно то, что вы хотите. "Только код конструктора для миграции "201311062215252_AddReaders" был повторно создан. Чтобы повторно создать шаблон всей миграции, используйте параметр -Force".
  6. Запустите update-Database , чтобы повторно применить последнюю миграцию с обновленными метаданными.
  7. Продолжить разработку или отправить в управление версиями (после выполнения модульных тестов).

Ниже приведено состояние базы локального кода Разработчика #2 после использования этого подхода.

Updated Metadata

Итоги

При использовании code First Migrations в среде группы возникают некоторые проблемы. Однако базовое понимание работы миграции и некоторые простые подходы к решению конфликт слияния позволяют легко преодолеть эти проблемы.

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