EF代码优先迁移以部署旧版本

16

我正在使用TFS Release Management进行持续集成和部署。

在部署过程中,我使用migrate.exe执行数据库迁移。当你从旧版本升级到新版本时,这个工具非常好用。但是,如果你想要部署旧版本的应用程序,情况就会变得复杂。

基本上,保存上下文迁移的程序集必须知道如何从版本3转换到版本2。通常,你使用即将部署的程序集作为迁移源,但是在这种情况下,你必须使用已经部署的程序集,因为它们是唯一知道如何从v3降到v2的程序集。(版本2甚至不知道v3的存在。)

我的当前计划是在部署过程中比较这两个程序集。如果安装目录中的程序集包含“更新”的迁移,而部署目录中的程序集没有,则我需要首先获取部署目录中可用的“最新”迁移,然后执行以下操作:

migrate.exe AssemblyInInstallationDir /targetMigration NewestFromAssemblyInDeploymentDir

在“正常”的部署场景中,您升级到新版本时只需执行以下操作:

migrate.exe AssemblyInDeploymentDir

这是否是一种合法的方法?我还没有研究使用EF库来评估每个程序集中可用的迁移。还有一个难题,每个程序集都是“相同”的,只是不同的版本。我可能需要将它们加载到单独的应用程序域中,然后使用跨应用程序域通信来获取所需的信息。
编辑
我创建了一个概念验证应用程序,可以列出同一程序集的两个不同版本的可用迁移。这对整个过程至关重要,因此我觉得值得记录。
该应用程序使用反射加载每个程序集,然后使用System.Data.Entity.Migrations中的DbMigrator类枚举迁移元数据。迁移名称以时间戳信息为前缀,因此可以对它们进行排序,从而查看包含“新”迁移的程序集。
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

1
我不知道这是否是一种被接受的做法,但是为你记录和分享你的方法点赞。 - Brendan Hannemann
2个回答

4

我们实际上已经解决了这个问题,并且已经使用我们的工具一年多来进行完全连续的数据库部署进入生产环境,没有人类参与。:)

我们在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


0
我通常的做法是(几乎)从不对数据库模式进行破坏性更改。这基本上是一种受控的技术债务形式。
例如,假设我要用ColumnY替换ColumnX。典型的方法是“将所有数据从ColumnX复制到ColumnY,然后从模式中删除ColumnX”。这会导致您无法回滚到以前的版本,因为ColumnX已经不存在了。
处理此问题的可回滚方式是添加ColumnY,复制数据,并添加触发器以使两个列彼此保持同步。这不是永久状态!“删除ColumnX和相关触发器”的用户故事立即进入待办列表,以备将来迭代使用,当我们确定我们永远不会回滚到依赖于ColumnX的版本时。
回滚仍然可能涉及发布先前版本的DACPAC,但必须确保不删除数据库中存在但不在模式中的项。这样,如果您更新了一堆存储过程以从ColumnY中提取数据,则可以发布从ColumnX中提取数据的旧版本,而旧版本并不知道模式已更改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接