如何管理在多个分支中进行迁移的项目?

69

我有一个ASP.NET MVC3项目,使用Entity Framework 4.3的代码优先方法。我使用迁移来保持数据库的最新状态。

该项目在源代码控制下,我有许多分支。我刚意识到的问题是,当我想将其中一个分支合并到主干时会出现问题。由于我在两个分支中创建了迁移文件,所以在合并时会有重叠的迁移,这可能会引起冲突。

在具有多个分支的项目中管理迁移有没有好的方法?

更新

一种方法是先合并,然后删除在分支分开时创建的所有迁移文件,然后创建一个新的迁移文件,包含从创建分支到合并回来期间的所有更改。这对于您可以转储数据库并使用所有迁移文件重新构建它的开发环境来说是可行的。然后的问题是生产环境。由于您不能回滚到分支创建时的时间而不冒失去数据的风险,因此,当您尝试使用新的迁移文件更新生产数据库时,就会出现冲突。

8个回答

22

在类似问题上,有一个更好的解决方案来处理实体框架迁移合并冲突

在合并后,你只需要重新生成目标分支中迁移元数据的脚手架。也就是说,你不需要重新生成上下文代码,只需重新生成 resx 文件中的状态。

add-migration [the_migration_to_rescaffold_metadata_for]

这种方法几乎总是有效的。如果合并中的其他迁移以某种方式更改了数据库,使得此迁移不再可运行或提供意外结果,则该过程将失败。也就是说 - 我相信这是一种非常罕见的情况,因为大多数迁移应该是自动生成的,或者至少不依赖于其他未在迁移本身中更改的表。

其中一个可能会导致重新生成状态失败的情况是:

  • 列foo是int类型,行包含[0, 1, 2]

  • 分支A中的迁移A将foo更改为布尔值(0将自动变为false,> 0将变为true)

  • 分支B中的迁移B将foo更改为字符串。它期望它是一个int类型,但它是一个布尔值,尽管迁移将成功。由于当创建迁移B时,行将包含["0","1","2"],因此数据将丢失。当迁移A将列更改为布尔值(并且成功且具有预期的结果)时,行现在将包含["0","1","1"],而迁移B的最终结果将与在分支B中观察到的不同。

可能还有更多边缘情况可能会导致解决方案出现问题。但是,如果迁移的上/下代码不依赖于合并中另一个迁移更改的内容,则仅更新迁移中的元数据应该可以很好地工作。


只有在从分支B合并到分支A,然后推送到主分支时才有效;如果分支A已经推送到主分支并部署到生产环境中,而您尝试从分支B执行此操作,则会失败。 - Bill Yang
请详细说明“this only works”和“try this from branch B”中的“This”。如果您已经在主分支中有A,则可以将主分支合并到B,重新构建,然后将B推送到主分支。您可能会遇到一些问题,请详细说明您所考虑的情况。 - oldwizard
如果您选择这种方法,可能会想要使用一个名为“IgnoreChanges”的标志。 - Joe Phillips
我在A和B分支中进行了不同的迁移更改,然后将B分支合并到A分支。这导致出现有关模型不相同的错误。我按照上面提出的建议创建了一个新的迁移并给它一个适当的名称。由于脚手架代码包含一个已经被先前迁移添加的字段,我清空了up/down方法,保存并运行了Update-Database。这解决了问题。 - smarty
我按照上述建议创建了一个新的迁移,该命令应替换最后一次迁移的状态。在您的情况下,分支A中进行的迁移正常工作,对吗?您合并到分支A的分支B中的迁移不起作用,因为迁移B中的状态与合并后的状态不匹配。您应该运行的命令是 add-migration the-full-name-of-the-migration-that-was-merged-from-branch-b,它将替换B迁移中的基本状态以匹配分支A中的最后一次迁移。您不应该创建新的迁移。 - oldwizard
被接受的答案说“被接受的答案是不正确的”。我感到头晕 ;) - Павле

20

编辑:我的同事发现了一种更简单的方法,为了完整性,我将原始答案保留在底部。

(非常重要)在生产环境中进行迁移时,必须确保与当前分支中的迁移不冲突,否则您需要重新执行所有迁移并手动解决数据模型更改冲突。

  1. 使用生产环境数据还原开发数据库
  2. 运行update-database,它应该运行来自您的分支的迁移,并抱怨“无法更新数据库以匹配当前模型blah blah..”
  3. 运行add-migration MergeBranchBToMaster -ignoreChanges,这将创建一个空迁移。
  4. 再次运行update-database
  5. 推送您的更改

第3步的魔法基本上告诉EF不要关注不匹配的模型,因此请务必确保您的迁移不会与生产环境中的迁移冲突。如果确实有冲突,您可以始终为推送缺失的迁移创建SQL脚本(这实际上是首选方法)。

原始答案

我发现了一种基于@Ladislav Mrnka答案的相当直接的解决方案。这将与实时环境[1]一起使用,您只需小心不更改任何已部署的迁移即可。

  1. 在合并之前,请注意您添加的迁移(MyMigration)及其先前的迁移(BaseMigration)

  2. 合并git分支

  3. 打开程序包管理器控制台,并运行:UPDATE-DATABASE-TargetMigration:BaseMigration。这将使您的数据库恢复到应用任何冲突迁移之前的状态

  4. 删除本地迁移(MyMigration)

  5. 运行:UPDATE-DATABASE。这将应用在其他分支中完成的所有较新的迁移。

  6. 运行:ADD-MIGRATION MyMigration。这将基于当前数据库状态重新生成您的本地迁移,就像git-rebase一样。

  7. 运行:UPDATE-DATABASE。使用您的本地迁移更新数据库。

如果您有多个本地迁移,此方法也适用,但它将把它们合并为一个单独的迁移。

[1]通过与实时环境一起工作,我是指可以将生成的迁移应用于可能已经应用了其他分支的某些/全部迁移的实时环境。这些步骤仅用于开发目的。


1
如何还原现场数据库?如果他们一直在使用这个代码库进行迁移,还原将会使应用程序处于不一致状态,并可能删除用户数据。 - Jack
这些步骤不适用于实时环境,我添加了注释来解释其含义。 - Bill Yang
空迁移是一个相当不错的解决方案。 - Xriuk
空迁移是一个相当不错的解决方案。 - Xriuk

13

合并迁移是手动任务。部分迁移代码是自动生成的,通常不会合并自动生成的代码-相反,我们会在合并后再次运行自动生成代码。

在ADO.NET团队提供建议之前,我会遵循简单的原则:

  • 在执行合并之前,将主数据库还原到分支之前使用的版本
  • 合并你的分支
  • 从合并的程序集中排除在分支后创建的迁移类
  • 为合并的代码库添加一个新的迁移,该迁移将使你的数据库从分支之前的状态迁移到合并分支之后的状态
  • 如果你排除的迁移类包含一些自定义内容,请将它们合并到新的迁移类中
  • 运行迁移以将你的数据库迁移到当前合并版本

如果你的分支包含多个迁移步骤(版本),那么你将失去它们,并最终得到两个版本 - 分支之前和合并之后。

编辑:

它在生产环境中不起作用。问题在于开发过程本身。如果你有生产环境,你应该保持其分支不变(除了小bug修复)。如果你在该分支上继续进行开发并进行生产部署,同时在单独的分支中构建另一个版本而不进行持续集成(=持续将更改合并回主代码库以集成新开发),那么你就有了一个大问题。我认为总的来说,迁移无法处理这种情况。

在这种情况下,唯一的选择可能是从合并的解决方案中删除所有迁移,并从数据库中删除MigrationHistory表。然后你可以在项目上重新启用迁移,并添加初始迁移以使用当前数据库作为起点 = 没有关于先前迁移的任何信息,因此无法返回到先前版本。


谢谢你的回答!我在你回答的时候更新了我的问题,加入了一个类似的想法。你有什么关于如何管理实时环境的想法吗?请看我更新后的问题,里面有更多相关信息。 - Christofer Eliasson
感谢您的澄清。在我的情况下,由于我在一个单独的分支中开发新功能(这些功能尚未准备好用于生产),我想解决方案是不断将主分支合并到我的单独分支中,直到单独分支准备好合并回主分支。 - Christofer Eliasson
哎呀,这对我们来说是一个很大的伤害。最近我们不得不在现场环境中推送了一个“热修复”,其中包括迁移以添加一个新表。开发环境中的迁移正在从与现场环境不同的状态进行迁移。 - Chev
@Alex Ford 你可以在两个不同的分支中拥有相同的迁移上/下代码,但是在resx文件中对于该迁移有两个不同的状态。请参考我的回答。 - oldwizard
我也赞成在从特性分支合并到主分支时删除和重新生成迁移。特性分支迁移应该始终生成时间戳,这些时间戳在主分支中的所有迁移之后,这些迁移可能已经上线。当然,如果您定期将主分支合并到特性分支(您应该这样做),这意味着您还应该重新生成迁移,以便时间戳在主分支迁移之后。 - JustAMartin

12

Rowan Miller在Channel 9上制作了一个关于此主题的精彩视频:Migrations - Team Environments,它涉及到实体框架6。

它描述了这样一种情况:A和B开发人员正在开发同一模型,A先进行了检查操作。现在,当B从A那里获取最新版本时,他必须处理自己遇到的问题。

这与不同分支之间存在冲突本质上是相同的,因为一般问题是合并同时进行的迁移更改,但实际上具有不同的模型源状态。

解决方法是:

  • 在解决版本控制系统的冲突时,开发人员B必须接受自己和开发人员A的两个更改。
  • 此时,开发人员B的UpdateDatabase命令仍将失败(错误消息:“无法将数据库更新为匹配当前模型,因为存在未决的更改...”)
  • 开发人员B必须使用IgnoreChanges选项创建一个“空迁移”:

Add-Migration NameOfMigration -IgnoreChanges

然后,UpdateDatabase命令就会成功执行。


问题的来源

当更新数据库时发生错误的根本原因是EF在迁移文件的resx文件中存储了迁移所涉及的模型的快照。

在这种情况下,开发人员B获取/合并开发人员A所做更改后,“当前模型”的快照不正确。


这个视频解释了一切。在我看来,这应该是被接受的答案。 - Silas Hansen

4
我已经认真思考过这个问题,希望我能对此处呈现的不同观点和实践做出贡献。
在本地使用开发数据库时,添加列、添加新实体等操作时,我使用迁移以最方便的方式来更新数据库。因此,Add-Migration 会将“我的”当前模型(称为模型 b)与“我的”先前模型(模型 a)进行比较,并生成一个从 a 到 b 的迁移,用于更新数据库。
对我来说,如果每个人都有自己的数据库,并且组织中存在某些阶段/测试/开发/生产数据库服务器,则尝试合并我的迁移与其他人的迁移就没有多少意义。这都取决于团队的设置方式,但是,如果您想真正以分布式的方式工作,那么让彼此隔离变更是有意义的。
如果您以分布式方式工作,并且有一些实体(例如 Person)在进行处理。由于某种原因,还有很多其他人也在处理它。因此,您根据需要添加和删除 Person 上的属性,以适应 sprint 中的特定故事,例如社保号码,您首先将其转换为整数,因为您并不聪明,然后再转换为字符串等。
您添加 FirstName 和 LastName。
然后您完成了,并且您有十个奇怪的上下迁移(您可能在工作过程中删除了其中一些,因为它们只是垃圾),然后从中央 Git 存储库中获取了一些更改。哇。您的同事 Bob 也需要一些姓名,也许您应该互相交流一下?
无论如何,他已添加了 NameFirst 和 NameLast,我猜...那么你怎么办?好吧,您合并、重构和更改,以使其具有更合理的名称,例如 FirstName 和 LastName,然后运行测试并检查他的代码,然后将其推送到中央存储库。
但是迁移怎么办?好吧,现在就是时候制作一个迁移,将中心存储库或“测试”分支转移,包含从其模型 a 到模型 b 的一个不多不少的迁移。这个迁移只会有一个,而不是十个奇怪的迁移。
看到我的意思了吗?我们正在使用漂亮的小 POCO,对它们进行比较构成了实际的迁移。因此,在我看来,我们根本不需要合并迁移,而是应该每个分支都有自己的迁移。
实际上,在合并后的分支中是否需要创建迁移呢?是的,如果此数据库会自动更新,则需要。
我还需要更多的工作,至少这是我的想法。

这确实是一个有趣的想法。所以我猜你的意思是迁移文件根本不应该在源代码控制下吗? - Christofer Eliasson
一个使用案例是迁移包含某种逻辑的情况。如果您将修改后的 POCO 合并到不同的分支中,那么每个目标都必须创建类似的迁移。如果您忘记了迁移的非自动生成部分会发生什么?尽管我同意大多数迁移都是自动创建的,并且在需要时可以轻松地在目标分支中创建。 - oldwizard

2

考虑使用其他迁移库,例如FluentMigrator或Migrator.NET,以避免这些冲突。

我认为EF迁移在分支和合并方面还没有完全准备好 - 这需要大量的工作,并且很容易犯错误。


0

我认为@LavaEater所说的很有道理。我正在实施一个分支策略(开发、主干、发布),并将其与开发、QA和发布过程中的环境对齐。

  • 开发分支 - 本地开发
  • 主干分支 - 合并来自开发分支的更改并部署到我的暂存环境(Azure网站和SQL数据库)
  • 发布分支 - 合并来自主干的更改并部署到生产环境(另一个Azure网站和SQL数据库)

我遇到了上面讨论过的问题,我认为迁移的复杂性以及潜在的解决方法会给发布过程带来很大的风险。在开发、主干和发布中执行独立的迁移意味着我在Dev中构建的模式不是进入Staging的QA的模式,而QA在Staging上签署的模式也不是部署到Live的模式(除非我遵循其中一种建议的解决方案,但可能容易出错)。

回应@LavaEater - EF Code First真正的好处是什么?个人认为,它可以轻松地从代码生成模式(并且如果需要,可以调整自动生成的迁移)。之后,迁移是应该是一个简单的部署过程的复杂性。

我的当前想法是使用Code First在开发中生成迁移,然后选择以下方式之一:

  • 选项A)- 使用Update-Database -script来脚本化模式更改并将其放入源代码控制下。如果两个人修改同一个模型,则仍然存在一些冲突的可能性,但我认为这更容易管理。

  • 选项B)- 使用类似SQL Compare的工具来生成模式更改脚本。这可能更灵活和透明,因为我想看到我正在应用于生产数据库的确切模式更改(称我为偏执狂)。

我有遗漏什么吗?我想象中需要进行一些配置以在主分支和发布分支中禁用Code First迁移(假设DB将通过脚本创建和更新)。除此之外,它感觉像是一个安全的解决方案,但我会很重视第二个意见。


我同意,而缺失的部分是:DevOps 应该跟踪 current-migration 并将您的 schema/resx 与其进行比较。如果发现“迁移 schema 冲突”(不是代码冲突!),则应在 pull request 中通知开发人员。 - jmbmage

0

我知道这是一个旧帖子,但我刚遇到了类似的问题 - 在本地分支上有多个提交,由于某种原因,我在提交“x”中删除了包含表创建的迁移,并且不想手动重新创建它。最终,我删除了...ContextModelSnapshot.cs文件,将其替换为来自原始分支(例如develop)的文件,并执行add-migration以从最新模型生成干净的迁移。


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