共用方式為


小組環境中的 Code First 移轉

注意

本文假設您知道如何在基本案例中使用 Code First 移轉。 如果您不這麼做,則必須先閱讀 Code First 移轉 再繼續。

拿杯咖啡,您需要閱讀整篇文章

當兩位開發人員在其本機程式代碼基底中產生移轉時,小組環境中的問題大多是合併移轉。 雖然解決這些問題的步驟相當簡單,但您需要深入瞭解移轉的運作方式。 請不要直接跳到結尾, 花一點時間閱讀整篇文章,以確保您成功。

一些一般指導方針

在深入探討如何管理多個開發人員所產生的合併移轉之前,以下是一些一般指導方針來設定成功。

每個小組成員都應該有本機開發資料庫

移轉會使用 __MigrationsHistory 數據表來儲存已套用至資料庫的移轉。 如果您有多個開發人員在嘗試以相同資料庫為目標時產生不同的移轉(因此共用 __MigrationsHistory 數據表)移轉將會變得非常困惑。

當然,如果您有未產生移轉的小組成員,則沒有問題讓他們共用中央開發資料庫。

避免自動移轉

底線是自動移轉一開始在小組環境中看起來不錯,但實際上他們只是無法運作。 如果您想要知道原因,請繼續閱讀 – 如果沒有,您可以跳到下一節。

自動移轉可讓您更新資料庫架構以符合目前的模型,而不需要產生程式碼檔案(程序代碼型移轉)。 如果您只使用過自動移轉,且從未產生任何程式代碼型移轉,則自動移轉在小組環境中運作良好。 問題是,自動移轉受到限制,且不會處理許多作業 – 屬性/數據行重新命名、將數據移至另一個數據表等等。若要處理這些案例,您最終會產生程式代碼型移轉(以及編輯 Scaffolded 程式代碼),這些移轉會在自動移轉所處理的變更之間混合。 這可讓您在兩位開發人員簽入移轉時無法合併變更。

瞭解移轉的運作方式

在小組環境中成功使用移轉的關鍵,是基本瞭解移轉如何追蹤和使用模型的相關信息來偵測模型變更。

第一次移轉

當您將第一個移轉新增至專案時,您會在 封裝管理員 控制台中執行 Add-Migration First 之類的動作。 此命令執行的高階步驟如下圖。

第一次移轉

目前的模型是從您的程式代碼計算的 (1)。 然後,模型會計算所需的資料庫物件不同 (2) – 因為這是第一個移轉,模型只會使用空的模型進行比較。 必要的變更會傳遞至程式代碼產生器,以建置必要的移轉程序代碼 (3),然後新增至 Visual Studio 解決方案 (4)。

除了儲存在主要程式代碼檔案中的實際移轉程式代碼之外,移轉也會產生一些額外的程式代碼後置檔案。 這些檔案是移轉所使用的元數據,不是您應該編輯的元數據。 其中一個檔案是資源檔 (.resx),其中包含產生移轉時模型的快照集。 您將會在下一個步驟中看到如何使用此功能。

此時,您可能會執行 Update-Database 來將變更套用至資料庫,然後繼續實作應用程式的其他區域。

後續移轉

稍後您回來對模型進行一些變更– 在我們的範例中,我們會將 Url 屬性新增Blog。 接著,您會發出命令,例如 Add-Migration AddUrl 來建立移轉以套用對應的資料庫變更。 此命令執行的高階步驟如下圖。

第二次移轉

就像上次一樣,目前的模型是從程式代碼計算(1)。 不過,這次有現有的移轉,因此會從最新的移轉中擷取先前的模型(2)。 這兩個模型會依差異來尋找所需的資料庫變更 (3),然後程式會如先前一樣完成。

這個相同的程式會用於您新增至專案的任何進一步移轉。

為什麼要為模型快照集打擾?

您可能想知道為什麼 EF 對模型快照集進行困擾 , 為什麼不只查看資料庫。 如果是,請繼續閱讀。 如果您不感興趣,則可以略過本節。

EF 保留模型快照集的原因有很多:

  • 它可讓您的資料庫偏離 EF 模型。 這些變更可以直接在資料庫中進行,或者您可以變更移轉中的 Scaffold 程式代碼來進行變更。 以下是下列幾個實務範例:
    • 您想要將 Inserted 和 Updated 新增至數據行至一或多個數據表,但不想在 EF 模型中包含這些數據行。 如果移轉查看資料庫,則每次您建立移轉時,都會持續嘗試卸除這些數據行。 使用模型快照集,EF 只會偵測模型的合法變更。
    • 您想要變更預存程式的主體,以用於更新以包含一些記錄。 如果移轉從資料庫查看此預存程式,它會持續嘗試並將它重設為 EF 預期的定義。 使用模型快照集時,EF 只會在 EF 模型中變更程式的形狀時,使用 Scaffold 程式代碼來變更預存程式。
    • 這些相同的原則適用於新增額外的索引,包括資料庫中的額外數據表、將 EF 對應至位於數據表上的資料庫檢視等等。
  • EF 模型不僅包含資料庫的圖形。 讓整個模型可讓移轉查看模型中屬性和類別的相關信息,以及它們如何對應至數據行和數據表。 這項資訊可讓移轉在建構函式的程式代碼中更聰明。 例如,如果您變更屬性對應至移轉的數據行名稱,即可藉由查看其為相同的屬性來偵測重新命名,而如果您只有資料庫架構,則無法完成此動作。 

造成小組環境中的問題的原因

當您是處理應用程式的單一開發人員時,上一節所涵蓋的工作流程非常出色。 如果您是對模型進行變更的唯一人員,它也會在小組環境中運作良好。 在此案例中,您可以進行模型變更、產生移轉,並將其提交至原始檔控制。 其他開發人員可以同步處理您的變更,並執行 Update-Database 以套用架構變更。

當您有多個開發人員對 EF 模型進行變更並同時提交至原始檔控制時,就會開始發生問題。 EF 缺少的是將本機移轉與另一位開發人員自上次同步處理后已提交至原始檔控制移轉的第一類方式。

合併衝突的範例

首先,讓我們看看這類合併衝突的具體範例。 我們會繼續進行我們稍早查看的範例。 作為起點,讓我們假設原始開發人員已簽入上一節的變更。 我們會追蹤兩個開發人員,因為他們對程式代碼基底進行變更。

我們將透過一些變更來追蹤EF模型和移轉。 針對起點,這兩個開發人員已同步處理至原始檔控制存放庫,如下圖所示。

起點

開發人員 #1 和開發人員 #2 現在在其本機程式代碼基底中對 EF 模型進行一些變更。 開發人員 #1 會將 Rating 屬性新增至 Blog – 並產生 AddRating 移轉,以將變更套用至資料庫。 開發人員 #2 會將 Readers 屬性新增至 Blog , 併產生對應的 AddReaders 移轉。 這兩個開發人員都會執行 Update-Database,以將變更套用至其本機資料庫,然後繼續開發應用程式。

注意

移轉前面會加上時間戳,因此我們的圖形代表 AddReaders 從 Developer 移轉 #2 是在 AddRating 從開發人員移轉之後 #1。 開發人員 #1 或 #2 產生的移轉,都不會影響在小組中工作的問題,或合併他們的程式,我們將在下一節中查看。

本機變更

開發人員 #1 很幸運,因為開發人員剛好先提交變更。 因為自從他們同步處理存放庫之後,沒有人已簽入,所以他們只要提交變更,就不需要執行任何合併。

提交變更

現在是開發人員 #2 提交的時候了。 他們並不那麼幸運。 因為其他人在同步處理后已提交變更,所以必須提取變更並合併。 原始檔控制系統可能會自動合併程式代碼層級的變更,因為它們非常簡單。 下圖說明同步處理之後開發人員 #2 的本機存放庫狀態。 

從原始檔控制提取

在這個階段,開發人員 #2 可以執行 Update-Database ,它會偵測新的 AddRating 移轉(尚未套用至 Developer #2 的資料庫),並加以套用。 現在,Rating 數據行會新增至 Blogs 數據表,且資料庫與模型同步。

不過,有幾個問題:

  1. 雖然 Update-Database 會套用 AddRating 移轉,但也會引發警告:無法更新資料庫以符合目前的模型,因為有擱置的變更,而且已停用自動移轉...問題是,儲存在上次移轉中的模型快照集 (AddReader) 遺漏 Blog 上的 Rating 屬性(因為在產生移轉時不是模型的一部分)。 Code First 會偵測到上次移轉中的模型不符合目前的模型,並引發警告。
  2. 執行應用程式會導致 InvalidOperationException 指出「建立資料庫之後,支援 『BloggingContext』 內容的模型已變更。請考慮使用 Code First 移轉 來更新資料庫...同樣地,問題在於儲存在上次移轉中的模型快照集不符合目前的模型。
  3. 最後,我們預期現在執行 Add-Migration 會產生空的移轉(因為沒有套用至資料庫的變更)。 但是,因為移轉會將目前的模型與上一個移轉中的模型相比較(缺少 Rating 屬性),所以實際上會將另一個 AddColumn 呼叫 Scaffold 另一個 AddColumn 呼叫新增至 Rating 數據行。 當然,此移轉會在Update-Database期間失敗,因為 Rating 數據行已經存在。

解決合併衝突

好消息是,手動處理合併並不難,前提是您已瞭解移轉的運作方式。 因此,如果您已略過本節... 很抱歉,您需要先回去閱讀本文的其餘部分!

有兩個選項,最簡單的方式是產生空白移轉,此移轉具有正確的目前模型作為快照集。 第二個選項是更新上次移轉中的快照集,以擁有正確的模型快照集。 第二個選項比較困難,而且無法在每個案例中使用,但它也更簡潔,因為它不涉及新增額外的移轉。

選項 1:新增空白的 「合併」移轉

在此選項中,我們只為了確定最新的移轉具有儲存在其中的正確模型快照集而產生空白移轉。

不論產生上次移轉的人員為何,都可以使用此選項。 在範例中,我們已遵循開發人員 #2 負責合併,而且它們碰巧產生最後一個移轉。 但是,如果開發人員 #1 產生上次移轉,可以使用這些相同的步驟。 如果涉及多個移轉,也適用這些步驟 – 我們只是查看兩個,以便保持簡單。

下列程式可用於此方法,從您意識到您需要從原始檔控制同步的變更開始。

  1. 請確定本機程式代碼基底中的任何暫止模型變更都已寫入移轉。 此步驟可確保您在產生空白移轉時不會錯過任何合法的變更。
  2. 與原始檔控制同步。
  3. 執行 Update-Database 以套用其他開發人員簽入的任何新移轉。 注意:如果您沒有從Update-Database 命令收到任何警告,則沒有來自其他開發人員的新移轉,而且不需要執行任何進一步的合併。
  4. 執行 Add-Migration pick_a_name> –IgnoreChanges (例如 Add-Migration <Merge –IgnoreChanges)。 這會產生具有所有元數據的移轉(包括目前模型的快照集),但會在將目前模型與上次移轉中的快照集進行比較時,忽略它偵測到的任何變更(這表示您會取得空白 的 UpDown 方法)。
  5. 執行 Update-Database 以使用更新的元數據重新套用最新的移轉。
  6. 繼續開發,或提交至原始檔控制(當然執行單元測試之後)。

以下是使用此方法之後開發人員 #2 的本機程式代碼基底狀態。

合併移轉

選項 2:在上次移轉中更新模型快照集

此選項與選項 1 非常類似,但會移除額外的空白移轉,因為讓我們面對它,誰想要其解決方案中的額外程式碼檔案。

只有在最新的移轉只存在於本機程式代碼基底中,且尚未提交至原始檔控制時,這個方法才可行(例如,如果上次移轉是由執行合併的使用者所產生的)。 編輯其他開發人員可能已經套用至其開發資料庫的移轉元數據,甚至更糟地套用至生產資料庫,可能會導致非預期的副作用。 在程式中,我們將復原本機資料庫中的最後一個移轉,並以更新的元數據重新套用。

雖然上次移轉只需要在本機程式代碼基底中,但不會限制繼續移轉的數目或順序。 從多個不同的開發人員可以有多個移轉,而且適用相同的步驟– 我們只是查看了兩個,以保持簡單。

下列程式可用於此方法,從您意識到您需要從原始檔控制同步的變更開始。

  1. 請確定本機程式代碼基底中的任何暫止模型變更都已寫入移轉。 此步驟可確保您在產生空白移轉時不會錯過任何合法的變更。
  2. 與原始檔控制同步。
  3. 執行 Update-Database 以套用其他開發人員簽入的任何新移轉。 注意:如果您沒有從Update-Database 命令收到任何警告,則沒有來自其他開發人員的新移轉,而且不需要執行任何進一步的合併。
  4. 執行 Update-Database –TargetMigration second_last_migration>(在本範例中,我們遵循此範例會是 Update-Database –TargetMigration <AddRating)。 這樣會將資料庫復原回第二次移轉的狀態 ,實際上會「取消套用」資料庫的最後一次移轉。 注意:此步驟是為了讓移轉的元數據能夠安全編輯,因為元數據也會儲存在資料庫的__MigrationsHistoryTable中。這就是為什麼只有在上次移轉只在本機程式代碼基底中時,才應該使用此選項。如果其他資料庫已套用上次移轉,您也必須復原這些資料庫,並重新套用上次移轉以更新元數據。 
  5. 執行 Add-Migration full_name_including_timestamp_of_last_migration> (在此範例中,我們遵循此範例會像是 Add-Migration <201311062215252_AddReaders)。 注意:您需要包含時間戳,讓移轉知道您想要編輯現有的移轉,而不是建構新的移轉。這會更新上次移轉的元數據,以符合目前的模型。 當命令完成時,您會收到下列警告,但這正是您想要的。 「只有移轉 』201311062215252_AddReaders' 的設計工具程式代碼已重新建構。若要重新建構整個移轉,請使用 -Force 參數。
  6. 執行 Update-Database 以使用更新的元數據重新套用最新的移轉。
  7. 繼續開發,或提交至原始檔控制(當然執行單元測試之後)。

以下是使用此方法之後開發人員 #2 的本機程式代碼基底狀態。

已更新的元數據

摘要

在小組環境中使用 Code First 移轉 時,有一些挑戰。 不過,對移轉的運作方式有基本瞭解,以及解決合併衝突的一些簡單方法,可讓您輕鬆克服這些挑戰。

基本問題是儲存在最新移轉中的元數據不正確。 這會導致 Code First 錯誤地偵測到目前的模型和資料庫架構不符合,並在下一次移轉中建立不正確的程式碼。 使用正確的模型產生空白移轉,或更新最新移轉中的元數據,即可克服這種情況。