实体框架中的迁移在协作环境中的应用

51
我们有多个开发人员正在一个使用Entity Framework 5.0的项目上工作。每个开发者都使用自己的本地SQL 2012数据库,以便在不妨碍其他人的情况下进行开发和测试。
起初,我们使用了自动迁移和基于代码的迁移的混合方式。这根本行不通,所以我们决定禁用自动迁移,并只允许基于代码的迁移。我应该补充一点,我们重新开始使用一个干净的数据库,没有来自所有自动迁移的“损坏”的_MigrationsHistory。
所以现在的工作流程是:
1. 开发人员更改他的数据模型 2. 使用add-migration 并将其应用到他的数据库中,使用update-database。 3. 将数据模型更改和迁移提交到Git中。 4. 另一个开发人员拉取,接收更改,并将其应用到他的数据库中。
到目前为止,这个方法运作良好。然而,今天之前通常只有我制作迁移,其他人应用它们。但今天有三个开发人员的迁移。我刚刚拉取了这些迁移,做了一个update-database,一切顺利。
我自己的数据模型也进行了更改,因此在执行update-database之后,它警告我仍然不是最新的,所以我运行了add-migration <my migration>。但是当它生成迁移时,它给我展示了我已经应用到数据库中的所有迁移更改。因此:它尝试删除已经被删除的列,尝试创建已经存在的表等等。

这是怎么回事呢?我的假设是EF只会检查_MigrationsHistory表,并找出哪些迁移尚未在该表中出现,并按照名称中的时间戳顺序逐个应用它们。但显然不是这样,因为即使我撤销了自己的更改并且具有清洁的环境,它仍然抱怨我的数据库与模型不同步。但是我刚刚拉取了这些更改并将它们应用到了我的数据库中。它已经同步了。我也可以在_MigrationsHistory表中看到我刚刚应用的迁移。

我能想到的唯一可能是,我添加了一个属性到数据模型中,但不会导致数据库更改(我向数据模型Y添加了一个List<X>,其中X是一对多关系中的多。这不会导致数据库更改,因为X已经有一个指向Y的外键)。这可能是原因吗?如果是这样,那真的很脆弱,因为没有办法为此添加迁移,因为没有数据库更改,我也不确定如何解决这个问题。
我不确定该怎么处理这个问题,因为我当然可以编辑它生成的内容并删除已应用于我的数据库的所有内容。但接下来呢?我提交它,然后其他开发人员收到相同的消息,即他的数据库仍未更新,即使应用了我的新更改,他们自己进行了脚手架式的更改,得到了相同的无意义的脚手架,编辑它,提交它,然后下一个开发人员就会遇到同样的问题。这变成了一个恶性循环,类似于我们使用自动迁移时遇到的问题,我认为我们通过切换到仅基于代码解决了这个问题。我现在无法信任它执行正确的操作,像这样工作真是一场噩梦。
以下是翻译的结果:

我尝试了添加我从同事那里拉取的迁移,每次只添加一个,使用update-database -t:201211091112102_<migrationname>,但都没有成功。它仍然给我错误的脚手架。

那么我们做错了什么呢?或者EF根本不适合这样的协作方式?

更新

我创建了一个可重现的测试用例,但为了模拟这种多用户/多数据库的情况,需要进行一些冗长的操作。

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

当您拥有上述项目时,按照以下步骤进行复制(这些步骤也在代码中):
  1. add-migration Init(添加迁移项Init)
  2. update-database (对于数据库'TestDb'进行更新)
  3. 更改连接字符串以指向TestDb1(Change connection string to point to TestDb1)
  4. 在TestDb1上进行update-database更新
  5. 取消注释类Test上的属性Foo(Uncomment property Foo on class Test)
  6. 添加迁移项M1以将属性Foo添加到TestDb1中
  7. 再次注释掉Test.Foo
  8. 更改连接字符串以指向TestDb2(Change connection string to point to TestDb2)
  9. 从项目中排除迁移项M1,以便不将其应用于TestDb2中(Exclude migration M1 from project so it doesn't get applied to TestDb2)
  10. 取消注释类Test上的属性Bar
  11. update-database以将Init迁移应用于TestDb2(update-database to apply Init migration to TestDb2)
  12. 添加迁移项M2以将属性Bar添加到TestDb2中
  13. 再次更改连接字符串以指向原始的TestDb
  14. 再次将迁移项M1包含到项目中
  15. 取消注释类Test上的属性Foo
  16. 取消注释类Test上的属性SomeInt
  17. update-database更新数据库
  18. 添加迁移项M3,并因为迁移项M3尝试添加已由迁移项M1添加到数据库TestDb中的列Foo而产生错误
上面的内容是模拟三个用户,其中用户1初始化了他的数据库,其他两个用户也使用他的初始化来创建他们自己的数据库。然后用户2和用户3都对数据模型进行了更改,并将其与所需的迁移一起添加到源代码控制中。然后用户1拉取了用户2和3的更改,而用户1自己也对数据库进行了更改。然后用户1调用update-database来应用用户2和3的更改。然后他生成了自己的迁移,但错误地将用户2或3的更改添加到了生成的迁移中,导致在应用于用户1的数据库时出现错误。

你能否直接通过http://blogs.msdn.com/adonet/contact.aspx与我们(EF团队)联系,以便我们进一步帮助调查此事? - bricelam
@Brice - 当然,我会尝试建立一个可重现的测试案例。问题是,我不确定我能否重现,因为这是一件应该像这样工作的事情,对吧? - JulianR
@Brice - 请查看我问题的更新,我添加了一个测试用例,你可以使用它。我也会将其提交到那个联系表单中。 - JulianR
4
团队环境中的 Code First 迁移:http://msdn.microsoft.com/zh-cn/data/dn481501(注:该链接为微软官方文档,提供了关于在团队环境中使用 Code First 迁移的详细说明和指导。) - Colin
EF7的更新文档 - H. de Jonge
9个回答

21
您需要添加一个空的“合并”迁移,以重置.resx文件中最新迁移的快照。使用IgnoreChanges开关执行此操作: Add-Migration <迁移名称> -IgnoreChanges 有关说明,请参见此处

6
您需要像解决代码冲突一样手动解决迁移冲突。如果更新后有新的迁移,您需要确保最后一个迁移背后的元数据与当前模型匹配。要更新迁移的元数据,请重新发出 Add-Migration 命令。
例如,在您的情况中的第17步(Update-Database)之前,您应该发出以下命令:
Add-Migration M2

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


好的,谢谢。不过还不是很清楚要应用到哪个迁移上。今天我又遇到了另一个迁移冲突,就像我描述的那样,最终我解决了它,我认为是通过更新开发者的迁移元数据来解决的,而不是更新他刚拉取的迁移的元数据。我相信尝试这样做会给我带来一个“无法添加迁移,因为这些仍然是待处理的:...” 的错误。但是情况又有点混乱了,所以很难说我到底是怎么解决的。 - JulianR

5

2
我们在我们的环境中遇到了类似的问题,以下是我们目前已经发现和解决的内容:
当您应用了更改(update-database)但未检入时,然后您收到来自另一位开发人员的更改,而该开发人员没有您的更改时,这就是事情似乎失去同步的地方。根据我们的经验,当您执行update-database过程时,似乎会覆盖为您自己的更改保存的元数据,并由另一位开发人员的元数据进行替换。另一位开发人员没有您的更改,因此保存的元数据不再是对您的数据库的真实反映。在EF进行比较之后,由于元数据的更改,它“认为”您的更改实际上是新的。
一个简单但很丑陋的解决方法是做另一个迁移,并清空其内容,以便您有空的up()和empty down()方法。应用该迁移并将其检入源代码控制,让每个人都与之同步。这只是同步所有元数据,以便每个人都考虑到了所有更改。

1
我已经考虑过这个问题,希望我能为这里提出的不同观点和实践做出贡献。
请考虑本地迁移实际上代表了什么。在使用开发数据库时,当添加列等到表格,添加新实体等时,我使用迁移以最方便的方式更新数据库。
因此,Add-Migration会将我的当前模型(我们称之为模型b)与先前的模型(模型a)进行比较,并生成一个从a => b的迁移。
对我来说,如果每个人确实都有自己的数据库,组织中也存在某种阶段/测试/开发/生产数据库服务器,那么尝试将我的迁移与其他人的迁移合并就毫无意义了。这完全取决于团队如何设置,但是如果您想真正以分布式方式工作,那么隔离彼此免受其他人所做的更改是有意义的。
好吧,如果您在分布式环境下工作,并且有一些实体,例如Person,那么您可以在其上进行工作。由于某种原因,许多其他人也在使用它。因此,根据您在Sprint中特定的故事需要添加和删除Person属性,例如首先将社会安全号码转换为整数,因为您不太聪明,然后转换为字符串等。

你需要添加名字和姓氏。

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

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

但是迁移呢?现在是时候将中央存储库或特定于“test”分支的一个不错的小迁移从其模型a =>模型b移动到迁移了。这个迁移将是一个且仅一个迁移,而不是十个奇怪的迁移。

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

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

另一个需要考虑的事情是,在从中央仓库拉取之前,不要实际创建迁移。这意味着在创建迁移之前,您将获取其他团队成员的迁移代码模型更改。
还有一些工作要做,至少这是我的想法。

1

0

有一种简单的方法可以避免迁移时出现合并冲突/错误

  1. 像平常一样在你的分支上工作。
  2. 如果你合并到主分支时出现合并错误,则:
  3. migrations文件夹中删除所有*.cs文件。
  4. migrations文件夹内执行git checkout master ./*
  5. 重新创建你的迁移。
  6. 你的快照是最新的,没有合并冲突。
  7. 在合并请求到主分支之前,你需要先与主分支合并,并始终执行步骤3-6。

下面是一个简单的PowerShell脚本,它执行步骤3-6:

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"
}

我已经使用这种方法半年了。在迁移方面,没有合并冲突需要解决 8)。


0

我能想到的解决方案(至少适用于2个用户,尚未测试3个用户)是:

  1. 合并迁移以同步元数据,并运行update-database(这应该会失败),然后
  2. 执行add-database操作,接着
  3. 删除up()down()方法中生成的所有代码

这样仍然会由update database来运行,但不会做任何操作,只是将元数据同步更新。


0

我同意@LavaEater的观点。问题的核心似乎是迁移脚手架应该集中管理。也许可以作为某种自动化/集成构建过程的一部分,每次推送时都会进行?然后团队成员可以从服务器上拉取生成的迁移。

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


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