Подтвердить что ты не робот

EF Code Первые миграции для развертывания более старой версии

Я использую управление выпуском TFS для непрерывной интеграции и развертывания.

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

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

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

migrate.exe AssemblyInInstallationDir /targetMigration NewestFromAssemblyInDeploymentDir

Где, как в "обычном" сценарии развертывания при обновлении до более новой версии, вы можете просто:

migrate.exe AssemblyInDeploymentDir

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

ИЗМЕНИТЬ

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

Приложение использует отражение для загрузки каждой из сборок, а затем использует класс DbMigrator из System.Data.Entity.Migrations, чтобы перечислять метаданные миграции. Имена миграций имеют префикс с информацией о метке времени, что позволяет мне заказать их и посмотреть, какая сборка содержит "новый" набор миграций.

static void Main(string[] args)
{
    const string dllName = "Test.Data.dll";
    var assemblyCurrent = Assembly.LoadFile(Path.Combine(System.Environment.CurrentDirectory, string.Format("Current\\{0}", dllName)));
    var assemblyTarget = Assembly.LoadFile(Path.Combine(System.Environment.CurrentDirectory, string.Format("Target\\{0}", dllName)));

    Console.WriteLine("Curent Version: " + assemblyCurrent.FullName);
    Console.WriteLine("Target Version: " + assemblyTarget.FullName);

    const string contextName = "Test.Data.TestContext";
    const string migrationsNamespace = "Test.Data.Migrations";
    var currentContext = assemblyCurrent.CreateInstance(contextName);
    var targetContext = assemblyTarget.CreateInstance(contextName);

    var currentContextConfig = new DbMigrationsConfiguration
    {
        MigrationsAssembly = assemblyCurrent,
        ContextType = currentContext.GetType(),
        MigrationsNamespace = migrationsNamespace
    };

    var targetContextConfig = new DbMigrationsConfiguration
    {
        MigrationsAssembly = assemblyTarget,
        ContextType = targetContext.GetType(),
        MigrationsNamespace = migrationsNamespace
    };

    var migrator = new DbMigrator(currentContextConfig);
    var localMigrations = migrator.GetLocalMigrations(); //all migrations

    Console.WriteLine("Current Context Migrations:");
    foreach (var m in localMigrations)
    {
        Console.WriteLine("\t{0}", m);
    }

    migrator = new DbMigrator(targetContextConfig);
    localMigrations = migrator.GetLocalMigrations(); //all migrations

    Console.WriteLine("Target Context Migrations:");
    foreach (var m in localMigrations)
    {
        Console.WriteLine("\t{0}", m);
    }

    Console.ReadKey();
}

}

Результат приложения выглядит так:

Curent Version: Test.Data, Version=1.3.0.0, Culture=neutral, PublicKeyToken=null
Target Version: Test.Data, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null

Current Context Migrations:
    201403171700348_InitalCreate
    201403171701519_AddedAddresInfoToCustomer
    201403171718277_RemovedStateEntity
    201403171754275_MoveAddressInformationIntoContactInfo
    201403181559219_NotSureWhatIChanged
    201403181731525_AddedRowVersionToDomainObjectBase
Target Context Migrations:
    201403171700348_InitalCreate
    201403171701519_AddedAddresInfoToCustomer
    201403171718277_RemovedStateEntity
4b9b3361

Ответ 1

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

Мы опубликовали эту публикацию в GitHub: https://github.com/GalenHealthcare/Galen.Ef.Deployer

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

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

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

https://github.com/GalenHealthcare/Galen.Ef.Deployer/blob/master/Galen.Ci.EntityFramework.Deployer/Galen.Ci.EntityFramework.Testing/MigrationTestRunner.cs

Ответ 2

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

Например, скажем, я заменяю ColumnX на ColumnY. Типичный подход - "скопировать все данные из ColumnX в ColumnY, удалить ColumnX из схемы". Это убивает вашу способность откат к предыдущей версии, потому что ColumnX ушел.

Другим способом решения этой проблемы является добавление ColumnY, копирование данных и добавление триггеров для синхронизации обоих столбцов друг с другом. Это не предназначено для постоянного состояния! Пользовательская история для "Удалить столбцыX и связанные триггеры" сразу же идет на отставание, для будущей итерации, когда мы уверены, что мы никогда не откатимся от версии, зависящей от ColumnX.

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