简体   繁体   English

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

[英]Migrations in Entity Framework in a collaborative environment

We have multiple developers working on a project that uses Entity Framework 5.0.我们有多个开发人员在开发一个使用 Entity Framework 5.0 的项目。 Every developer uses his own local SQL 2012 database so he can develop and test without impeding others.每个开发人员都使用自己的本地 SQL 2012 数据库,因此他可以在不妨碍其他人的情况下进行开发和测试。

At first, we used a hybrid of automatic migrations and code-based migrations.起初,我们混合使用了自动迁移和基于代码的迁移。 That didn't work well at all so we decided to disable automatic migrations and to only allow code-based.这根本不起作用,因此我们决定禁用自动迁移并仅允许基于代码的迁移。 I should add that we started again with a clean database without a 'corrupted' _MigrationsHistory from all the automatic migrations.我应该补充一点,我们从一个干净的数据库开始,没有来自所有自动迁移的“损坏” _MigrationsHistory

So now the workflow is:所以现在的工作流程是:

  1. Developer changes his datamodel开发人员更改他的数据模型
  2. Does add-migration <Name> and applies it to his database with update-database .执行add-migration <Name>并使用update-database将其应用到他update-database
  3. Checks in the datamodel change and the migration into Git.检查数据模型更改和迁移到 Git。
  4. Another developer pulls, receives the changes and applies it to his database.另一个开发人员拉取、接收更改并将其应用到他的数据库中。

So far, this worked well.到目前为止,这运行良好。 However before today it was usually just me who made the migrations and the others applied them.然而,在今天之前,通常只有我进行迁移,其他人应用它们。 But today there were migrations from three developers.但是今天有来自三个开发人员的迁移。 I just pulled those migrations, did an update-database which went fine.我只是拉动了那些迁移,做了一个运行良好的update-database

I also had a change to my own datamodel however so at the end of the update-database it gave me a warning that I still wasn't up to date so I did add-migration <my migration> .我也对我自己的数据模型进行了更改,因此在update-database结束时它给了我一个警告,我仍然不是最新的,所以我做了add-migration <my migration> However when it scaffolded the migration, it gave me the changes of all the migrations I had already applied to the database.然而,当它为迁移搭建脚手架时,它给了我已经应用于数据库的所有迁移的更改。 So: it tried to drop columns that had already been dropped, tried to create a table that already existed, etc.所以:它试图删除已经删除的列,尝试创建一个已经存在的表,等等。

How can that be?怎么可能? My assumption was that EF would just check the _MigrationsHistory table and find out which migrations weren't present in the table yet and apply those one by one ordered by the timestamp that's part of the name.我的假设是 EF 将只检查_MigrationsHistory表并找出表中尚不存在的迁移,并按名称中的时间戳排序一一应用这些迁移。 But apparently not, because even when I undo my own changes and I have a clean environment it still complains my database isn't in sync with the model.但显然不是,因为即使我撤消自己的更改并且我有一个干净的环境,它仍然会抱怨我的数据库与模型不同步。 But I just pulled those changes and applied them to my database.但我只是提取了这些更改并将它们应用到我的数据库中。 It is in sync.同步的。 I can see the migrations that I just applied in the _MigrationsHistory table too.我也可以在_MigrationsHistory表中看到我刚刚应用的迁移。

The only thing I can think of is that I added a property to a datamodel that wouldn't result in a database change (I added a List<X> to datamodel Y where X is the many in the one-to-many relationship. This wouldn't result in a database change as X already had a foreign key to Y).我唯一能想到的是我向数据模型添加了一个不会导致数据库更改的属性(我向数据模型 Y 添加了一个List<X> ,其中 X 是一对多关系中的多个。这不会导致数据库更改,因为 X 已经有 Y 的外键)。 Could that be it?会是这样吗? If so, that's really fragile because there's no way to add a migration for that since there's no database change and I'm not sure how to fix this either.如果是这样,那真的很脆弱,因为没有办法为此添加迁移,因为没有数据库更改,我也不知道如何解决这个问题。

I'm not sure how to deal with this, because I can of course just edit what it scaffolded and remove everything that has already been applied to my database.我不确定如何处理这个问题,因为我当然可以编辑它的脚手架并删除已经应用于我的数据库的所有内容。 But then what?但是然后呢? I check it in and then some other developer gets the same message that his database isn't up to date even after applying my new changes, scaffolds his own changes, gets the same nonsense scaffolding, edits it, checks it in and then the next developer gets it.我签入它然后其他一些开发人员收到相同的消息,即使在应用我的新更改后,他的数据库也不是最新的,搭建他自己的更改,获取相同的废话搭建,编辑它,签入然后下一个开发人员得到它。 It becomes a vicious circle and a similar one to what we had when we used automatic migrations and I thought we had fixed that by switching to code-based only.它变成了一个恶性循环,类似于我们使用自动迁移时的情况,我认为我们已经通过切换到仅基于代码的方式解决了这个问题。 I can't trust it right now to do the right thing and it's a nightmare to work with like this.我现在不能相信它会做正确的事情,像这样工作是一场噩梦。

What I also tried is adding the migrations I pulled from my coworkers one by one with update-database -t:201211091112102_<migrationname> but to no avail.我还尝试过使用update-database -t:201211091112102_<migrationname>添加我从同事update-database -t:201211091112102_<migrationname>但无济于事。 It still gives me the erroneous scaffold.它仍然给了我错误的脚手架。

So what did we do wrong here, or is EF simply not built for collaboration like this?那么我们在这里做错了什么,或者 EF 根本不是为这样的协作而构建的?

UPDATE更新

I created a reproducible test case, it's a bit of a lengthy dance though in order to simulate this multi user/multi database scenario.我创建了一个可重现的测试用例,虽然为了模拟这个多用户/多数据库场景,它有点冗长。

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

Steps to reproduce when you have the above project (these steps are also present in the code):拥有上述项目后的重现步骤(这些步骤也存在于代码中):

  1. add-migration Init添加迁移初始化
  2. update-database (on database 'TestDb')更新数据库(在数据库“TestDb”上)
  3. Change connection string to point to TestDb1将连接字符串更改为指向 TestDb1
  4. update-database on TestDb1 TestDb1 上的更新数据库
  5. Uncomment property Foo on class Test在类 Test 上取消注释属性 Foo
  6. add-migration M1 to add property Foo to TestDb1 add-migration M1 将属性 Foo 添加到 TestDb1
  7. Comment out Test.Foo again再次注释掉 Test.Foo
  8. Change connection string to point to TestDb2将连接字符串更改为指向 TestDb2
  9. Exclude migration M1 from project so it doesn't get applied to TestDb2从项目中排除迁移 M1,因此它不会应用于 TestDb2
  10. Uncomment property Bar on class Test取消注释类 Test 上的属性栏
  11. update-database to apply Init migration to TestDb2更新数据库以将 Init 迁移应用到 TestDb2
  12. add-migration M2 to add property Bar to TestDb2 add-migration M2 将属性栏添加到 TestDb2
  13. Change connection string to point to the original TestDb again更改连接字符串以再次指向原始 TestDb
  14. Include migration M1 into the project again再次将迁移 M1 包含到项目中
  15. Uncomment property Foo on class Test在类 Test 上取消注释属性 Foo
  16. Uncomment property SomeInt on class Test取消注释类 Test 上的 SomeInt 属性
  17. update-database更新数据库
  18. add-migration M3添加迁移 M3
  19. update-database, get an error because M3 tries to add column Foo to database TestDb which was already just added by migration M1.更新数据库,因为 M3 尝试将列 Foo 添加到数据库 TestDb 中,该数据库已由迁移 M1 添加,因此出现错误。

The above is to simulate three users, where user 1 inits his database, the other two use his initialization to create their database as well.上面是模拟三个用户,其中用户1初始化他的数据库,另外两个也使用他的初始化来创建他们的数据库。 Then user 2 and user 3 both make their own change to the datamodel and add it to source control together with the migrations needed to apply the changes.然后用户 2 和用户 3 都对数据模型进行自己的更改,并将其与应用更改所需的迁移一起添加到源代码管理中。 Then user 1 pulls the changes of user 2 and 3 while user 1 has also made a change to the database himself.然后用户 1 拉取用户 2 和 3 的更改,而用户 1 自己也对数据库进行了更改。 Then user 1 calls update-database to apply the changes of user 2 and 3. He then scaffolds his own migration which then erroneously adds a change from user 2 or 3 to the scaffolded migration which causes an error when applied to user 1's database.然后,用户 1 调用update-database来应用用户 2 和 3 的更改。然后他搭建自己的迁移,然后错误地将用户 2 或 3 的更改添加到搭建的迁移,这在应用于用户 1 的数据库时会导致错误。

You need to add a blank "merge" migration that will reset the snapshot of the latest migration in the .resx file.您需要添加一个空白的“合并”迁移,它将重置 .resx 文件中最新迁移的快照。 Do this using the IgnoreChanges switch:使用 IgnoreChanges 开关执行此操作:

Add-Migration <migration name> -IgnoreChanges

See here for an explanation这里的解释

You need to manually resolve migration conflicts just like you would code conflicts.您需要像处理代码冲突一样手动解决迁移冲突。 If you update and there are new migrations, you need to ensure that the metadata behind the last migration matches the current model.如果更新并且有新的迁移,则需要确保上次迁移背后的元数据与当前模型匹配。 To update the metadata of the migration, re-issue the Add-Migration command for it.要更新迁移的元数据,请为其重新发出 Add-Migration 命令。

For example, before step 17 (Update-Database) in your scenario, you should issue the following command例如,在您的场景中的第 17 步(更新数据库)之前,您应该发出以下命令

Add-Migration M2

This will update the metadata to bring it in sync with your current model.这将更新元数据以使其与您当前的模型同步。 Now when you try and add M3, it should be blank since you have not made any further model changes.现在,当您尝试添加 M3 时,它应该是空白的,因为您还没有进行任何进一步的模型更改。

Option 1: Add a blank 'merge' migration选项 1:添加空白的“合并”迁移

  1. Ensure any pending model changes in your local code base have been written to a migration.确保本地代码库中的任何挂起的模型更改都已写入迁移。 This step ensures you don't miss any legitimate changes when it comes time to generate the blank migration.此步骤可确保您在生成空白迁移时不会错过任何合法更改。
  2. Sync with source control.与源代码管理同步。
  3. Run Update-Database to apply any new migrations that other developers have checked in. ** Note:****if you don't get any warnings from the Update-Database command then there were no new migrations from other developers and there is no need to perform any further merging.运行 Update-Database 以应用其他开发人员已签入的任何新迁移。 ** 注意:****如果您没有从 Update-Database 命令收到任何警告,则说明没有来自其他开发人员的新迁移,并且有无需执行任何进一步合并。
  4. Run Add-Migration –IgnoreChanges (eg Add-Migration Merge –IgnoreChanges).运行 Add-Migration –IgnoreChanges(例如 Add-Migration Merge –IgnoreChanges)。 This generates a migration with all the metadata (including a snapshot of the current model) but will ignore any changes it detects when comparing the current model to the snapshot in the last migrations (meaning you get a blank Up and Down method).这会生成包含所有元数据(包括当前模型的快照)的迁移,但将忽略在将当前模型与上次迁移中的快照进行比较时检测到的任何更改(意味着您将获得空白的 Up 和 Down 方法)。
  5. Continue developing, or submit to source control (after running your unit tests of course).继续开发,或提交到源代码控制(当然是在运行单元测试之后)。

Option 2: Update the model snapshot in the last migration选项 2:更新上次迁移中的模型快照

  1. Ensure any pending model changes in your local code base have been written to a migration.确保本地代码库中的任何挂起的模型更改都已写入迁移。 This step ensures you don't miss any legitimate changes when it comes time to generate the blank migration.此步骤可确保您在生成空白迁移时不会错过任何合法更改。
  2. Sync with the source control.与源代码管理同步。
  3. Run Update-Database to apply any new migrations that other developers have checked in. ** Note:****if you don't get any warnings from the Update-Database command then there were no new migrations from other developers and there is no need to perform any further merging.运行 Update-Database 以应用其他开发人员已签入的任何新迁移。 ** 注意:****如果您没有从 Update-Database 命令中收到任何警告,则说明没有来自其他开发人员的新迁移,并且有无需执行任何进一步合并。
  4. Run Update-Database –TargetMigration (in the example we've been following this would be Update-Database –TargetMigration AddRating).运行 Update-Database –TargetMigration(在我们一直遵循的示例中,这将是 Update-Database –TargetMigration AddRating)。 This roles the database back to the state of the second last migration – effectively 'un-applying' the last migration from the database.这使数据库恢复到最后一次迁移的状态——有效地“取消应用”数据库中的最后一次迁移。 ** Note:****This step is required to make it safe to edit the metadata of the migration since the metadata is also stored in the __MigrationsHistoryTable of the database. ** 注意:****需要此步骤以确保编辑迁移的元数据安全,因为元数据也存储在数据库的 __MigrationsHistoryTable 中。 This is why you should only use this option if the last migration is only in your local code base.这就是为什么如果最后一次迁移仅在您的本地代码库中,您应该只使用此选项。 If other databases had the last migration applied you would also have to roll them back and re-apply the last migration to update the metadata.如果其他数据库应用了上次迁移,您还必须回滚它们并重新应用上次迁移以更新元数据。
  5. Run Add-Migration (in the example we've been following this would be something like Add-Migration 201311062215252_AddReaders).运行 Add-Migration(在我们一直遵循的示例中,这类似于 Add-Migration 201311062215252_AddReaders)。 ** Note:****You need to include the timestamp so that migrations knows you want to edit the existing migration rather than scaffolding a new one. ** 注意:****您需要包含时间戳,以便迁移知道您要编辑现有迁移而不是构建新迁移。 This will update the metadata for the last migration to match the current model.这将更新上次迁移的元数据以匹配当前模型。 You'll get the following warning when the command completes, but that's exactly what you want.命令完成后,您将收到以下警告,但这正是您想要的。 “Only the Designer Code for migration '201311062215252_AddReaders' was re-scaffolded. “仅重新构建了迁移的设计器代码 '201311062215252_AddReaders'。 To re-scaffold the entire migration, use the -Force parameter.”要重新构建整个迁移,请使用 -Force 参数。”
  6. Run Update-Database to re-apply the latest migration with the updated metadata.运行 Update-Database 以使用更新的元数据重新应用最新的迁移。
  7. Continue developing, or submit to source control (after running your unit tests of course).继续开发,或提交到源代码控制(当然是在运行单元测试之后)。

MSDN have a great article on this. MSDN 在这方面有一篇很棒的文章。 Please go through it.请通过它。

Entity Framework Code First Migrations in Team Environments 团队环境中的实体框架代码优先迁移

We are having similar issues in our environment, here is what we've figured out so far and how we got around it:我们在我们的环境中遇到了类似的问题,以下是我们迄今为止发现的问题以及我们如何解决它:

When you have changes that you have applied (update-database) but not checked in, and then you receive changes from another developer who doesn't have your changes, this is where things seem to get out of sync.如果您有已应用的更改(更新数据库)但未签入,然后您从另一个没有更改的开发人员那里收到更改,这就是事情似乎不同步的地方。 In our experience, it seems like the meta data that is saved for your own changes get over written by the meta-data from the other developer when you do the update-database process.根据我们的经验,当您执行更新数据库过程时,似乎为您自己的更改保存的元数据会被其他开发人员的元数据覆盖。 The other developer doesn't have your changes, so the meta-data that gets saved is no longer a real reflection of your database.其他开发人员没有您的更改,因此保存的元数据不再是您数据库的真实反映。 When EF does a comparison after that, it 'thinks' that your changes are actually new again because of the meta data change.当 EF 在此之后进行比较时,它“认为”您的更改实际上又是新的,因为元数据更改。

A simple, admittedly ugly workaround is to do another migration, and wipe out it's contents so you have empty up() and empty down() methods.一个简单的、公认的丑陋的解决方法是进行另一次迁移,并清除它的内容,这样你就有了空的 up() 和空的 down() 方法。 Apply that migration and check it into source control and let everyone sync to that.应用该迁移并将其签入源代码管理,让每个人都同步到该迁移。 This simply syncs up all of the meta data so everyone has all of the changes accounted for.这只是同步所有元数据,因此每个人都可以考虑所有更改。

I have added an issue on codeplex, this issue causes many a head scratching in our team too.我在 codeplex 上添加了一个问题,这个问题在我们的团队中也引起了很多人的注意。

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

I have put some thought into this and I hope I will contribute to the different opinions and practices presented here.我对此进行了一些思考,我希望我能对这里提出的不同意见和做法做出贡献。

Consider what your local migrations actually represent.考虑一下您的本地迁移实际代表什么。 When working locally with a dev database, I use migrations to update the database in the most convenient way possible when adding columns etc to tables, adding new entities etc.在本地使用开发数据库时,在向表中添加列等、添加新实体等时,我使用迁移以最方便的方式更新数据库。

So, Add-Migration checks my current model (let's call it model b) against my previous model (model a) and generates a migration to go from a => b in the database.因此,Add-Migration 根据我之前的模型(模型 a)检查我当前的模型(我们称之为模型 b),并生成从数据库中的 a => b 开始的迁移。

To me it makes very little sense to try and merge my migrations with anyone elses migrations, if everyone indeed has their own database and there then exists some kind of stage / test / dev / production database servers in the organization.对我来说,尝试将我的迁移与其他任何人的迁移合并是没有意义的,如果每个人确实有自己的数据库,然后在组织中存在某种阶段/测试/开发/生产数据库服务器。 This all depends on how the team has it set up, but it makes sense to insulate each other from changes that other people make if you want to truly work in a distributed manner.这一切都取决于团队的设置方式,但如果您想真正以分布式方式工作,那么将彼此与其他人所做的更改隔离开来是有意义的。

Well, if you work distributed and have some entity, Person, for example, that you work on.好吧,如果您分布式工作并且有一些实体,例如,您正在处理的人员。 For some reason, lots of other people are also working on it.出于某种原因,很多其他人也在研究它。 So, you add and remove properties on Person as needed for your particular story in the sprint (we're all working agile here, aren't we?), like Social Security number that you first made into an integer because you aren't that bright and then to a string etc.因此,您可以根据需要为冲刺中的特定故事添加和删除 Person 上的属性(我们都在这里敏捷工作,不是吗?)那个明亮,然后到一个字符串等。

You add FirstName And LastName.您添加名字和姓氏。

You are then done and you have ten weird up and down migrations (you probably removed some of them while working since they were just crap) and you fetch some changes from the central Git repo.然后你就完成了,你有十次奇怪的上下迁移(你可能在工作时删除了其中的一些,因为它们只是废话)并且你从中央 Git 存储库中获取一些更改。 Wow.哇。 Your colleague Bob also needed some names, maybe you should've talked to each other?你的同事鲍勃也需要一些名字,也许你们应该互相谈谈?

Anyways, he has added NameFirst and NameLast, I guess... so what do you do?不管怎样,他已经添加了NameFirst 和NameLast,我猜……那你怎么办? Well, you merge, refactor, change so it has more sane names... like FirstName and LastName, you run your tests and check his code, and then you push to the central.好吧,您合并、重构、更改以使其具有更合理的名称……例如 FirstName 和 LastName,您运行测试并检查他的代码,然后推送到中央。

But what about the migrations?但是迁移呢? Well, now would be the time to make a migration moving the central repo, or the branch "test" more specifically, contain a nice little migration from its model a => model b.好吧,现在是时候进行迁移,移动中央存储库,或者更具体地说,分支“测试”包含从其模型 a => 模型 b 的漂亮小迁移。 This migration will be one and only one migration, not ten weird ones.这次迁移将是一次且只有一次迁移,而不是十次奇怪的迁移。

Do you see what I'm getting at?你明白我在说什么吗? We are working with nice little pocos and the comparisons of them constitute the actual migrations.我们正在使用漂亮的小 pocos,它们的比较构成了实际的迁移。 So, we shouldn't merge migrations at all, in my opinion, we should have migrations-per-branch or something like that.所以,我们根本不应该合并迁移,在我看来,我们应该有每个分支的迁移或类似的东西。

In fact, do we even need to create the migration in the branch after merge?事实上,我们甚至需要在合并后在分支中创建迁移吗? Yes, if this database is updated automatically, we need to.是的,如果这个数据库是自动更新的,我们需要。

Another thing to consider is to never actually creating a migration before doing a pull from the central repo.另一件要考虑的事情是,在从中央仓库拉取数据之前,永远不要真正创建迁移。 That means you will both get the other team members' migration code and their changes to the model before creating your migration.这意味着你都得到其他团队成员的迁移代码他们创建迁移之前模型的变化。

Gotta work some more, those are my thoughts on this, at least.还得再努力一些,至少这是我对此的看法。

The solution I was able to come up with (at least for 2 users, haven't tested for 3) is:我能够想出的解决方案(至少对于 2 个用户,尚未测试 3 个)是:

  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删除up()down()方法中所有生成的代码

this will still be run by update database but won't do anything, just bringing the metadata up to sync.这仍将由更新数据库运行,但不会做任何事情,只是将元数据同步。

I agree with @LavaEater.我同意@LavaEater。 The core of the issue, it would seem, is that migration scaffolding should be centralised.问题的核心似乎是迁移脚手架应该集中化。 Perhaps as part of some automated/integrated build process each time a push occurs?也许作为每次推送发生时某些自动化/集成构建过程的一部分? Thereafter the resulting migrations can be pulled from the server by team-members.此后,团队成员可以从服务器中提取由此产生的迁移。

This means that their own migration scripts should not be pushed to the server.这意味着不应将他们自己的迁移脚本推送到服务器。

There is an easy way to have no merge conflicts/errors with migrations.有一种简单的方法可以避免迁移发生合并冲突/错误

  1. Work on your branch as you would do at any time.像在任何时候一样在您的分支上工作。
  2. If you merge to master and have merge errors then:如果您合并到 master 并出现合并错误,则:
  3. remove all *.cs files from migrations folder.migrations文件夹中删除所有 *.cs 文件。
  4. do git checkout master ./* inside migrations folder.migrations文件夹中执行git checkout master ./*
  5. Recreate your migration.重新创建您的迁移。
  6. Your snapshot is up2date and there is no merge conflict.您的快照是 up2date 并且没有合并冲突。
  7. Also just before merging pull request to master you need to merge with master and do steps 3-6 ALWAYS.同样在将 pull request 合并到 master 之前,您需要与 master 合并并始终执行步骤 3-6。

Below is simple Powershell script that does steps 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"
}

I am working with that method for last half year.过去半年我一直在使用这种方法。 0 merge conflicts to resolve when it comes to migrations 8). 0 合并冲突要解决迁移时 8)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM