![](/img/trans.png)
[英]Entity Framework Core how to execute migrations in production environment
[英]Migrations in Entity Framework in a collaborative environment
我們有多個開發人員在開發一個使用 Entity Framework 5.0 的項目。 每個開發人員都使用自己的本地 SQL 2012 數據庫,因此他可以在不妨礙其他人的情況下進行開發和測試。
起初,我們混合使用了自動遷移和基於代碼的遷移。 這根本不起作用,因此我們決定禁用自動遷移並僅允許基於代碼的遷移。 我應該補充一點,我們從一個干凈的數據庫開始,沒有來自所有自動遷移的“損壞” _MigrationsHistory
。
所以現在的工作流程是:
add-migration <Name>
並使用update-database
將其應用到他update-database
。 到目前為止,這運行良好。 然而,在今天之前,通常只有我進行遷移,其他人應用它們。 但是今天有來自三個開發人員的遷移。 我只是拉動了那些遷移,做了一個運行良好的update-database
。
我也對我自己的數據模型進行了更改,因此在update-database
結束時它給了我一個警告,我仍然不是最新的,所以我做了add-migration <my migration>
。 然而,當它為遷移搭建腳手架時,它給了我已經應用於數據庫的所有遷移的更改。 所以:它試圖刪除已經刪除的列,嘗試創建一個已經存在的表,等等。
怎么可能? 我的假設是 EF 將只檢查_MigrationsHistory
表並找出表中尚不存在的遷移,並按名稱中的時間戳排序一一應用這些遷移。 但顯然不是,因為即使我撤消自己的更改並且我有一個干凈的環境,它仍然會抱怨我的數據庫與模型不同步。 但我只是提取了這些更改並將它們應用到我的數據庫中。 它是同步的。 我也可以在_MigrationsHistory
表中看到我剛剛應用的遷移。
我唯一能想到的是我向數據模型添加了一個不會導致數據庫更改的屬性(我向數據模型 Y 添加了一個List<X>
,其中 X 是一對多關系中的多個。這不會導致數據庫更改,因為 X 已經有 Y 的外鍵)。 會是這樣嗎? 如果是這樣,那真的很脆弱,因為沒有辦法為此添加遷移,因為沒有數據庫更改,我也不知道如何解決這個問題。
我不確定如何處理這個問題,因為我當然可以編輯它的腳手架並刪除已經應用於我的數據庫的所有內容。 但是然后呢? 我簽入它然后其他一些開發人員收到相同的消息,即使在應用我的新更改后,他的數據庫也不是最新的,搭建他自己的更改,獲取相同的廢話搭建,編輯它,簽入然后下一個開發人員得到它。 它變成了一個惡性循環,類似於我們使用自動遷移時的情況,我認為我們已經通過切換到僅基於代碼的方式解決了這個問題。 我現在不能相信它會做正確的事情,像這樣工作是一場噩夢。
我還嘗試過使用update-database -t:201211091112102_<migrationname>
添加我從同事update-database -t:201211091112102_<migrationname>
但無濟於事。 它仍然給了我錯誤的腳手架。
那么我們在這里做錯了什么,或者 EF 根本不是為這樣的協作而構建的?
更新
我創建了一個可重現的測試用例,雖然為了模擬這個多用戶/多數據庫場景,它有點冗長。
https://github.com/JulianR/EfMigrationsTest/
擁有上述項目后的重現步驟(這些步驟也存在於代碼中):
上面是模擬三個用戶,其中用戶1初始化他的數據庫,另外兩個也使用他的初始化來創建他們的數據庫。 然后用戶 2 和用戶 3 都對數據模型進行自己的更改,並將其與應用更改所需的遷移一起添加到源代碼管理中。 然后用戶 1 拉取用戶 2 和 3 的更改,而用戶 1 自己也對數據庫進行了更改。 然后,用戶 1 調用update-database
來應用用戶 2 和 3 的更改。然后他搭建自己的遷移,然后錯誤地將用戶 2 或 3 的更改添加到搭建的遷移,這在應用於用戶 1 的數據庫時會導致錯誤。
您需要添加一個空白的“合並”遷移,它將重置 .resx 文件中最新遷移的快照。 使用 IgnoreChanges 開關執行此操作:
Add-Migration <migration name> -IgnoreChanges
看這里的解釋
您需要像處理代碼沖突一樣手動解決遷移沖突。 如果更新並且有新的遷移,則需要確保上次遷移背后的元數據與當前模型匹配。 要更新遷移的元數據,請為其重新發出 Add-Migration 命令。
例如,在您的場景中的第 17 步(更新數據庫)之前,您應該發出以下命令
Add-Migration M2
這將更新元數據以使其與您當前的模型同步。 現在,當您嘗試添加 M3 時,它應該是空白的,因為您還沒有進行任何進一步的模型更改。
選項 1:添加空白的“合並”遷移
- 確保本地代碼庫中的任何掛起的模型更改都已寫入遷移。 此步驟可確保您在生成空白遷移時不會錯過任何合法更改。
- 與源代碼管理同步。
- 運行 Update-Database 以應用其他開發人員已簽入的任何新遷移。 ** 注意:****如果您沒有從 Update-Database 命令收到任何警告,則說明沒有來自其他開發人員的新遷移,並且有無需執行任何進一步合並。
- 運行 Add-Migration –IgnoreChanges(例如 Add-Migration Merge –IgnoreChanges)。 這會生成包含所有元數據(包括當前模型的快照)的遷移,但將忽略在將當前模型與上次遷移中的快照進行比較時檢測到的任何更改(意味着您將獲得空白的 Up 和 Down 方法)。
- 繼續開發,或提交到源代碼控制(當然是在運行單元測試之后)。
選項 2:更新上次遷移中的模型快照
- 確保本地代碼庫中的任何掛起的模型更改都已寫入遷移。 此步驟可確保您在生成空白遷移時不會錯過任何合法更改。
- 與源代碼管理同步。
- 運行 Update-Database 以應用其他開發人員已簽入的任何新遷移。 ** 注意:****如果您沒有從 Update-Database 命令中收到任何警告,則說明沒有來自其他開發人員的新遷移,並且有無需執行任何進一步合並。
- 運行 Update-Database –TargetMigration(在我們一直遵循的示例中,這將是 Update-Database –TargetMigration AddRating)。 這使數據庫恢復到最后一次遷移的狀態——有效地“取消應用”數據庫中的最后一次遷移。 ** 注意:****需要此步驟以確保編輯遷移的元數據安全,因為元數據也存儲在數據庫的 __MigrationsHistoryTable 中。 這就是為什么如果最后一次遷移僅在您的本地代碼庫中,您應該只使用此選項。 如果其他數據庫應用了上次遷移,您還必須回滾它們並重新應用上次遷移以更新元數據。
- 運行 Add-Migration(在我們一直遵循的示例中,這類似於 Add-Migration 201311062215252_AddReaders)。 ** 注意:****您需要包含時間戳,以便遷移知道您要編輯現有遷移而不是構建新遷移。 這將更新上次遷移的元數據以匹配當前模型。 命令完成后,您將收到以下警告,但這正是您想要的。 “僅重新構建了遷移的設計器代碼 '201311062215252_AddReaders'。 要重新構建整個遷移,請使用 -Force 參數。”
- 運行 Update-Database 以使用更新的元數據重新應用最新的遷移。
- 繼續開發,或提交到源代碼控制(當然是在運行單元測試之后)。
MSDN 在這方面有一篇很棒的文章。 請通過它。
我們在我們的環境中遇到了類似的問題,以下是我們迄今為止發現的問題以及我們如何解決它:
如果您有已應用的更改(更新數據庫)但未簽入,然后您從另一個沒有更改的開發人員那里收到更改,這就是事情似乎不同步的地方。 根據我們的經驗,當您執行更新數據庫過程時,似乎為您自己的更改保存的元數據會被其他開發人員的元數據覆蓋。 其他開發人員沒有您的更改,因此保存的元數據不再是您數據庫的真實反映。 當 EF 在此之后進行比較時,它“認為”您的更改實際上又是新的,因為元數據更改。
一個簡單的、公認的丑陋的解決方法是進行另一次遷移,並清除它的內容,這樣你就有了空的 up() 和空的 down() 方法。 應用該遷移並將其簽入源代碼管理,讓每個人都同步到該遷移。 這只是同步所有元數據,因此每個人都可以考慮所有更改。
我在 codeplex 上添加了一個問題,這個問題在我們的團隊中也引起了很多人的注意。
我對此進行了一些思考,我希望我能對這里提出的不同意見和做法做出貢獻。
考慮一下您的本地遷移實際代表什么。 在本地使用開發數據庫時,在向表中添加列等、添加新實體等時,我使用遷移以最方便的方式更新數據庫。
因此,Add-Migration 根據我之前的模型(模型 a)檢查我當前的模型(我們稱之為模型 b),並生成從數據庫中的 a => b 開始的遷移。
對我來說,嘗試將我的遷移與其他任何人的遷移合並是沒有意義的,如果每個人確實有自己的數據庫,然后在組織中存在某種階段/測試/開發/生產數據庫服務器。 這一切都取決於團隊的設置方式,但如果您想真正以分布式方式工作,那么將彼此與其他人所做的更改隔離開來是有意義的。
好吧,如果您分布式工作並且有一些實體,例如,您正在處理的人員。 出於某種原因,很多其他人也在研究它。 因此,您可以根據需要為沖刺中的特定故事添加和刪除 Person 上的屬性(我們都在這里敏捷工作,不是嗎?)那個明亮,然后到一個字符串等。
您添加名字和姓氏。
然后你就完成了,你有十次奇怪的上下遷移(你可能在工作時刪除了其中的一些,因為它們只是廢話)並且你從中央 Git 存儲庫中獲取一些更改。 哇。 你的同事鮑勃也需要一些名字,也許你們應該互相談談?
不管怎樣,他已經添加了NameFirst 和NameLast,我猜……那你怎么辦? 好吧,您合並、重構、更改以使其具有更合理的名稱……例如 FirstName 和 LastName,您運行測試並檢查他的代碼,然后推送到中央。
但是遷移呢? 好吧,現在是時候進行遷移,移動中央存儲庫,或者更具體地說,分支“測試”包含從其模型 a => 模型 b 的漂亮小遷移。 這次遷移將是一次且只有一次遷移,而不是十次奇怪的遷移。
你明白我在說什么嗎? 我們正在使用漂亮的小 pocos,它們的比較構成了實際的遷移。 所以,我們根本不應該合並遷移,在我看來,我們應該有每個分支的遷移或類似的東西。
事實上,我們甚至需要在合並后在分支中創建遷移嗎? 是的,如果這個數據庫是自動更新的,我們需要。
另一件要考慮的事情是,在從中央倉庫拉取數據之前,永遠不要真正創建遷移。 這意味着你都得到其他團隊成員的遷移代碼和他們創建遷移之前模型的變化。
還得再努力一些,至少這是我對此的看法。
我能夠想出的解決方案(至少對於 2 個用戶,尚未測試 3 個)是:
up()
和down()
方法中所有生成的代碼這仍將由更新數據庫運行,但不會做任何事情,只是將元數據同步。
我同意@LavaEater。 問題的核心似乎是遷移腳手架應該集中化。 也許作為每次推送發生時某些自動化/集成構建過程的一部分? 此后,團隊成員可以從服務器中提取由此產生的遷移。
這意味着不應將他們自己的遷移腳本推送到服務器。
有一種簡單的方法可以避免遷移發生合並沖突/錯誤。
migrations
文件夾中刪除所有 *.cs 文件。migrations
文件夾中執行git checkout master ./*
。下面是執行步驟 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)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.