50

我们有多个开发人员正在开发一个使用 Entity Framework 5.0 的项目。每个开发人员都使用自己的本地 SQL 2012 数据库,因此他可以在不妨碍他人的情况下进行开发和测试。

起初,我们混合使用了自动迁移和基于代码的迁移。这根本不起作用,所以我们决定禁用自动迁移并只允许基于代码的迁移。我应该补充一点,我们从一个干净的数据库重新开始,而_MigrationsHistory所有自动迁移都没有“损坏”。

所以现在的工作流程是:

  1. 开发人员更改他的数据模型
  2. 使用.add-migration <Name>将其应用于他的数据库update-database
  3. 检查数据模型更改并迁移到 Git。
  4. 另一个开发人员提取、接收更改并将其应用到他的数据库。

到目前为止,这运作良好。然而在今天之前,通常只有我进行迁移,其他人应用它们。但是今天有来自三个开发人员的迁移。我刚刚取消了那些迁移,做得update-database很好。

我也对自己的数据模型进行了更改,但是在最后update-database它给了我一个警告,说我仍然不是最新的,所以我做了add-migration <my migration>。但是,当它为迁移搭建脚手架时,它给了我已经应用到数据库的所有迁移的更改。所以:它试图删除已经被删除的列,试图创建一个已经存在的表,等等。

这个怎么可能?我的假设是 EF 只会检查_MigrationsHistory表并找出表中尚不存在哪些迁移,并按名称中的时间戳顺序逐一应用这些迁移。但显然不是,因为即使我撤消自己的更改并且我有一个干净的环境,它仍然会抱怨我的数据库与模型不同步。但我只是提取了这些更改并将它们应用到我的数据库中。它同步的。我也可以看到我刚刚在_MigrationsHistory表格中应用的迁移。

我唯一能想到的是我向数据模型添加了一个不会导致数据库更改的属性(我List<X>向数据模型 Y 添加了一个,其中 X 是一对多关系中的多个。这不会导致数据库更改,因为 X 已经有 Y 的外键)。会是这样吗?如果是这样,那真的很脆弱,因为没有办法为此添加迁移,因为没有数据库更改,我也不知道如何解决这个问题。

我不确定如何处理这个问题,因为我当然可以编辑它所搭建的内容并删除已经应用于我的数据库的所有内容。但是然后呢?我签入它,然后其他一些开发人员收到相同的消息,即使在应用我的新更改后,他的数据库也不是最新的,搭建他自己的更改,获得相同的废话脚手架,编辑它,签入,然后下一个开发商明白了。它变成了一个恶性循环,与我们使用自动迁移时的情况类似,我认为我们已经通过切换到仅基于代码的方式解决了这个问题。我现在不能相信它会做正确的事情,像这样工作简直是一场噩梦。

我还尝试过添加我从同事那里提取的迁移,update-database -t:201211091112102_<migrationname>但无济于事。它仍然给了我错误的脚手架。

那么我们在这里做错了什么,或者 EF 根本不是为这样的协作而构建的?

更新

我创建了一个可重现的测试用例,但为了模拟这种多用户/多数据库场景,这有点冗长。

https://github.com/JulianR/EfMigrationsTest/

当您拥有上述项目时重现的步骤(这些步骤也存在于代码中):

  1. 添加迁移初始化
  2. 更新数据库(在数据库“TestDb”上)
  3. 更改连接字符串以指向 TestDb1
  4. TestDb1 上的更新数据库
  5. 取消注释类 Test 上的属性 Foo
  6. add-migration M1 将属性 Foo 添加到 TestDb1
  7. 再次注释掉 Test.Foo
  8. 更改连接字符串以指向 TestDb2
  9. 从项目中排除迁移 M1,使其不会应用于 TestDb2
  10. 取消注释类 Test 上的属性 Bar
  11. 更新数据库以将初始化迁移应用到 TestDb2
  12. add-migration M2 将属性 Bar 添加到 TestDb2
  13. 更改连接字符串以再次指向原始 TestDb
  14. 再次将迁移 M1 包含到项目中
  15. 取消注释类 Test 上的属性 Foo
  16. 取消注释类 Test 上的属性 SomeInt
  17. 更新数据库
  18. 添加迁移 M3
  19. 更新数据库,因为 M3 尝试将 Foo 列添加到数据库 TestDb 中,该列已经由迁移 M1 添加。

以上是模拟三个用户,其中用户1初始化他的数据库,另外两个使用他的初始化来创建他们的数据库。然后用户 2 和用户 3 都对数据模型进行了自己的更改,并将其与应用更改所需的迁移一起添加到源代码管理中。然后用户 1 拉取用户 2 和 3 的更改,而用户 1 自己也对数据库进行了更改。然后用户 1 调用update-database以应用用户 2 和 3 的更改。然后他构建自己的迁移,然后错误地将用户 2 或 3 的更改添加到脚手架迁移中,这在应用于用户 1 的数据库时会导致错误。

4

9 回答 9

20

您需要添加一个空白的“合并”迁移,它将重置 .resx 文件中最新迁移的快照。使用 IgnoreChanges 开关执行此操作:

Add-Migration <migration name> -IgnoreChanges

请参阅此处以获取说明

于 2014-04-01T21:47:14.277 回答
6

您需要像处理代码冲突一样手动解决迁移冲突。如果您更新并且有新的迁移,则需要确保上次迁移背后的元数据与当前模型匹配。要更新迁移的元数据,请为其重新发出 Add-Migration 命令。

例如,在您的场景中的第 17 步(更新数据库)之前,您应该发出以下命令

Add-Migration M2

这将更新元数据以使其与您当前的模型同步。现在,当您尝试添加 M3 时,它应该是空白的,因为您还没有进行任何进一步的模型更改。

于 2012-11-12T18:13:48.237 回答
5

选项 1:添加一个空白的“合并”迁移

  1. 确保本地代码库中的任何待定模型更改都已写入迁移。此步骤可确保您在生成空白迁移时不会错过任何合法更改。
  2. 与源代码管理同步。
  3. 运行 Update-Database 以应用其他开发人员签入的任何新迁移。**注意:****如果您没有从 Update-Database 命令中收到任何警告,则其他开发人员没有新的迁移,并且有无需执行任何进一步的合并。
  4. 运行 Add-Migration –IgnoreChanges(例如 Add-Migration Merge –IgnoreChanges)。这会生成包含所有元数据(包括当前模型的快照)的迁移,但在将当前模型与上次迁移中的快照进行比较时会忽略它检测到的任何更改(这意味着您会得到一个空白的 Up 和 Down 方法)。
  5. 继续开发,或提交到源代码控制(当然是在运行单元测试之后)。

选项 2:更新上次迁移中的模型快照

  1. 确保本地代码库中的任何待定模型更改都已写入迁移。此步骤可确保您在生成空白迁移时不会错过任何合法更改。
  2. 与源代码管理同步。
  3. 运行 Update-Database 以应用其他开发人员签入的任何新迁移。**注意:****如果您没有从 Update-Database 命令中收到任何警告,则其他开发人员没有新的迁移,并且有无需执行任何进一步的合并。
  4. 运行 Update-Database –TargetMigration(在我们一直遵循的示例中,这将是 Update-Database –TargetMigration AddRating)。这使数据库恢复到最后一次迁移的状态——有效地从数据库中“取消应用”最后一次迁移。** 注意:****此步骤是确保编辑迁移的元数据安全所必需的,因为元数据也存储在数据库的 __MigrationsHistoryTable 中。这就是为什么您应该仅在最后一次迁移仅在您的本地代码库中时才使用此选项的原因。如果其他数据库应用了最后一次迁移,您还必须回滚它们并重新应用最后一次迁移以更新元数据。
  5. 运行 Add-Migration(在我们一直遵循的示例中,这类似于 Add-Migration 201311062215252_AddReaders)。** 注意:**** 您需要包含时间戳,以便迁移知道您要编辑现有迁移而不是构建新迁移。这将更新上次迁移的元数据以匹配当前模型。当命令完成时,您将收到以下警告,但这正是您想要的。“只有用于迁移 '201311062215252_AddReaders' 的设计器代码被重新搭建。要重新构建整个迁移,请使用 -Force 参数。”
  6. 运行 Update-Database 以使用更新的元数据重新应用最新迁移。
  7. 继续开发,或提交到源代码控制(当然是在运行单元测试之后)。

MSDN 对此有一篇很棒的文章。请通过它。

团队环境中的实体框架代码优先迁移

于 2017-08-07T10:38:48.380 回答
2

我们在我们的环境中遇到了类似的问题,以下是我们迄今为止发现的问题以及我们如何解决它:

当您有已应用的更改(更新数据库)但未签入时,然后您从另一个没有您更改的开发人员那里收到更改,这就是事情似乎不同步的地方。根据我们的经验,当您执行更新数据库过程时,为您自己的更改保存的元数据似乎被其他开发人员的元数据覆盖。其他开发人员没有您的更改,因此保存的元数据不再是您数据库的真实反映。当 EF 在那之后进行比较时,它“认为”您的更改实际上又是新的,因为元数据发生了变化。

一个简单但丑陋的解决方法是进行另一次迁移,并清除它的内容,这样您就有了 empty up() 和 empty down() 方法。应用该迁移并将其签入源代码控制,并让每个人都同步到它。这只是同步了所有元数据,因此每个人都可以考虑所有更改。

于 2012-11-13T04:37:46.103 回答
1

我在 codeplex 上添加了一个问题,这个问题也在我们的团队中引起了很多人的头疼。

链接是https://entityframework.codeplex.com/workitem/1670

于 2013-09-18T14:07:45.570 回答
1

我对此进行了一些思考,我希望我能为这里提出的不同观点和实践做出贡献。

考虑一下您的本地迁移实际代表什么。在本地使用开发数据库时,我在向表中添加列等、添加新实体等时,使用迁移以最方便的方式更新数据库。

因此,Add-Migration 将我当前的模型(我们称之为模型 b)与我之前的模型(模型 a)进行对比,并生成从数据库中的 a => b 开始的迁移。

对我来说,尝试将我的迁移与其他任何人的迁移合并是没有意义的,如果每个人都确实拥有自己的数据库,并且组织中存在某种阶段/测试/开发/生产数据库服务器。这一切都取决于团队是如何设置的,但是如果您想真正以分布式方式工作,那么将彼此与其他人所做的更改隔离开来是有意义的。

好吧,如果你在分布式工作并且有一些实体,例如,你工作的人。出于某种原因,许多其他人也在研究它。因此,您可以根据 sprint 中特定故事的需要在 Person 上添加和删除属性(我们在这里都在灵活地工作,不是吗?),例如您首先将社会安全号码设为整数,因为您不是那个明亮,然后到一个字符串等。

您添加名字和姓氏。

然后你就完成了,你有十个奇怪的上下迁移(你可能在工作时删除了其中一些,因为它们只是垃圾),你从中央 Git 存储库获取一些更改。哇。你的同事鲍勃也需要一些名字,也许你们应该互相谈谈?

无论如何,他已经添加了 NameFirst 和 NameLast,我猜……那你怎么办?好吧,您合并、重构、更改,使其具有更合理的名称……例如 FirstName 和 LastName,您运行测试并检查他的代码,然后推送到中央。

但是迁移呢?好吧,现在是时候进行迁移中央仓库了,或者更具体地说,分支“测试”包含从其模型 a => 模型 b 的一个不错的小迁移。这次迁移将是一次且只有一次迁移,而不是十次奇怪的迁移。

你明白我在说什么吗?我们正在使用漂亮的小 pocos,它们的比较构成了实际的迁移。所以,我们根本不应该合并迁移,在我看来,我们应该有每个分支的迁移或类似的东西。

事实上,我们甚至需要在合并之后在分支中创建迁移吗?是的,如果这个数据库是自动更新的,我们需要。

要考虑的另一件事是,在从中央仓库拉取数据之前,永远不要真正创建迁移。这意味着在创建迁移之前,您将同时获得其他团队成员的迁移代码及其对模型的更改。

必须再努力一些,至少这些是我对此的想法。

于 2013-12-12T10:00:40.983 回答
0

The solution I was able to come up with (at least for 2 users, haven't tested for 3) is:

  1. merging migrations to sync up the meta-data run update-database (this should fail), then
  2. add-database and then
  3. delete all of the generated code in up() and down() methods

this will still be run by update database but won't do anything, just bringing the metadata up to sync.

于 2013-11-07T17:26:25.997 回答
0

我同意@LavaEater。看来,问题的核心是移民脚手架应该集中化。每次推送发生时,也许作为一些自动化/集成构建过程的一部分?此后,团队成员可以从服务器中提取结果迁移。

这意味着不应将他们自己的迁移脚本推送到服务器。

于 2014-02-24T19:02:56.067 回答
0

有一种简单的方法可以避免与迁移发生合并冲突/错误。

  1. 随时在您的分支上工作。
  2. 如果您合并到 master 并出现合并错误,则:
  3. 从文件夹中删除所有 *.cs 文件migrations
  4. git checkout master ./*里面的migrations文件夹。
  5. 重新创建迁移。
  6. 您的快照是 up2date 并且没有合并冲突。
  7. 同样,在将拉取请求合并到 master 之前,您需要与 master 合并并始终执行步骤 3-6。

下面是执行步骤 3-6 的简单 Powershell 脚本:

function Write-Info($text)
{
    Write-Color "$pwd", "> ", "$text" -Colour "Yellow", "Blue", "White"
}
function Create-Migration($project, $migrationName, $referenceBranch)
{
    Set-Location "$SolutionPath\$project"
    Write-Info "Going to migrations"
    Set-Location "Migrations"
    Write-Info "Removing ./*.cs"
    Remove-Item ./*.cs
    Write-Info "git fetch --all"
    git fetch --all
    Write-Info "git checkout origin/$referenceBranch ./*"
    git checkout origin/$referenceBranch ./*
    Set-Location ..
    Write-Info "Creating migration $migrationName "
    dotnet ef migrations add "$migrationName"
}

在过去的半年里,我一直在使用这种方法。0 迁移时要解决的合并冲突 8).

于 2021-02-11T11:00:47.543 回答