我正在使用TFS版本管理进行持续集成和部署。
我正在使用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
答案 0 :(得分:2)
我们实际上已经解决了这个问题,并且已经使用我们的工具一年多来完全连续地将数据库部署到生产中。没有人参与。 :)
我们已经在GitHub上公开了一些内容:https://github.com/GalenHealthcare/Galen.Ef.Deployer
你可以打破"打破"变化,但一般来说,我们也避免这种情况 - 但主要是因为我们的应用程序在升级过程中仍然存在。我们将数据层视为可独立部署的组件 - 因此,它具有"接口"需要保持兼容。
我们经常使用多阶段升级方法,我们部署一个向后/向后兼容的中间版本,升级我们的各种应用程序服务,然后最终升级数据库层以消除传统兼容性。
即使在这种情况下,我们也能够自动从/到任何版本的架构和数据。事实上,我们已经添加了单元测试,每次为每个数据库版本构建时都会验证这一点。它基本上是在模式迭代链中上下移动,并验证向上和向下迁移始终有效并保持数据一致性和兼容性。您可以在GitHub项目中看到这些测试。这是一个例子:
答案 1 :(得分:0)
我通常采用的方法是(几乎)永远不会对我的数据库架构进行重大更改。它基本上是一种受控形式的技术债务。
例如,假设我正在用ColumnY替换ColumnX。典型的方法是“将所有数据从ColumnX复制到ColumnY,从架构中删除ColumnX”。这会导致您回滚到之前版本的能力,因为ColumnX已经消失。
回滚友好的解决方法是添加ColumnY,复制数据,并添加触发器以使两列保持同步。这不是一个永久的状态!当我们确定我们永远不会回滚到依赖于ColumnX的版本时,“删除ColumnX和关联的触发器”的用户故事会立即出现在待办事项中,以供将来的迭代使用。
回滚仍然可能涉及发布以前版本的DACPAC,但需要注意的是,您必须确保不删除数据库中不存在于模式中的项目。这样,如果您更新了一堆存储过程以从ColumnY中提取,则可以发布从ColumnX中提取的旧版本,而旧版本幸福地不知道架构已更改。