2016 年 8 月

第 31 卷,第 8 期

本文章是由機器翻譯。

資料點 EF 核心變更追蹤的行為︰ 未變更、已修改及已新增

Julie Lerman

Julie Lerman您看到我的處理方式,此資料行標題中嗎? 您可能會有視為未變更、 修改和加入列舉的 EntityState 中 Entity Framework (EF)。也可以協助我說明相較於舊版的 Entity Framework EF 核心中的變更追蹤的行為。變更追蹤已經成為在 EF 核心更一致,這樣就可以更確信知道當您使用中斷連接的資料有何影響。

請記住雖然 EF 核心嘗試保持範例和許多舊版的 Entity Framework 的語法,EF 核心是一組新的 Api — 完全重新撰寫程式碼基底從頭。因此,務必不假設所有項目將行為完全一樣在過去。變更追蹤是重要的例子。

因為 EF 核心的第一個反覆項目為目標,配合 ASP.NET 核心,很多工作著重在中斷連線的狀態,也就是確定 Entity Framework 可以處理來自超出訊號範圍,物件的狀態,其中 EF 尚未已追蹤的那些物件。典型的案例是 Web API,可接受來自用戶端應用程式保存到資料庫物件。

在 3 月 2016年資料點專欄中,我提到 」 處理狀態的中斷連線的實體在 EF 」 (msdn.com/magazine/mt694083)。那篇文章著重於將狀態資訊指派給中斷連接的實體,並傳遞回 EF 的變更追蹤的那些物件時,與 EF 分享該資訊。雖然我使用 EF6 配置範例,該模式是仍有相關的 EF 核心,所以討論過之後 EF 核心行為,我將告訴我 EF 核心中實作此模式的方式的範例。

利用 DbSet 追蹤︰ 修改日期

DbSet 一定會包含加入、 附加和移除方法。在單一物件上的這些方法的結果都是很簡單,這些物件的狀態設定為相關的 EntityState。結果中加入、 附加在未變更和移除變更將狀態加入到已刪除。沒有一個例外狀況,就是,如果您移除已經稱為附加,它就會中斷 DbContext 因為不再需要追蹤新的實體的實體。  EF6,在圖形中,使用這些方法時的效果相關的物件沒有相當一致。無法移除先前未被追蹤的物件,並會擲回錯誤。已追蹤的物件可能會或可能沒有變更,取決於這些狀態的是其狀態。我在 EF6 中建立一組測試以了解各種不同的行為,您可以在 GitHub 找到 bit.ly/28YvwYd

在建立時 EF 核心,EF 小組實驗整個 beta 版的這些方法的行為。在 EF 核心 RTM 方法不再運作中 EF6 或更早版本一樣。大多數的情況下,這些方法所做的變更會導致更一致的行為,您可以使用。但是請務必了解如何變更它們。

 當您使用 [新增] 時,附加和移除的物件圖形附加,圖形中的每個物件的狀態的未知的變更追蹤會設定為方法所識別的狀態。讓我釐清這使用我最愛的 EF 核心模型從 「 七個 Samurai 」 影片,影片 samurais 引號,以及其他相關的資訊。

Samurai 新型且不受到追蹤,如果 Samurais.Add 會將該 samurai 狀態設定為 Added。如果 samurai 報價時呼叫 Add 附加至它,其狀態也會附加至設定。這是所要的行為,而且事實上,就跟 EF6 是相同。

如果您要加入至現有的 samurai 的新報價,並遵循我的建議設定 newQuote.SamuraiId Samurai.Id 的值,而改為設定導覽屬性時,newQuote.Samurai=oldSamurai。在中斷連接案例,其中報價和 oldSamurai 都不正在追蹤的 ef,Quotes.Add(newQuote) 會執行與前面相同。它會將標記為加入 newQuote 和新增為 oldSamurai。SaveChanges 會插入資料庫的兩個物件,而且您必須在資料庫中重複 oldSamurai。

如果您是在用戶端應用程式,比方說,Windows Presentation Foundation (WPF),並使用您要查詢的內容來 samurais,然後使用該相同的內容執行個體呼叫內容而變更。Quotes.Add(newQuote),內容已經知道 oldSamurai,並將其不變的狀態變更為新增。這是我的意思不變更已追蹤物件的狀態。

變更追蹤會影響已中斷連線的圖形中的相關的物件的方法是值得注意的是不同,並在 EF 核心中使用這些方法時,您應該保留在考慮這些差異。

Rowan Miller 摘要說明新的行為在 GitHub 問題 (bit.ly/295goxw):

新增︰ 將每個連線到已經不追蹤的實體。

附加︰ 將可連線的每個實體,附加除外,其中連線到實體有存放區產生的索引鍵,而未指定任何索引鍵的值;這些會標示為加入。

更新: 相同附加,但實體會標示為已修改。

移除: 相同 Attach,然後標示為已刪除的根目錄。串聯刪除,因為現在都發生在 SaveChanges,這可讓稍後流動到實體的串聯規則。

一個詳細的變更,您可能會注意到這份清單中的 DbSet 方法是︰ DbSet 最後有更新方法,將未追蹤物件的狀態設定為已修改。采! 什麼是不錯的替代方式永遠不必加入或附加,然後明確地將狀態設定為已修改。

DbSet 範圍的方法︰ 也會修改

DbSet (AddRange 和 RemoveRange) 上的兩個範圍方法 EF6 中導入,並且讓您傳遞的陣列,例如型別中。這提供很明顯的效能提升,因為變更追蹤程式執行一次,而不是陣列的每個項目上。方法沒有呼叫 Add 和先前移除的詳細,因此,您必須考慮如何相關的物件會受影響的圖形。

在 EF6,range 方法存在僅適用於加入和移除,但 UpdateRange 和 AttachRange,現在將帶 EF 核心。如先前所述,行為會與每個物件或圖形傳遞到範圍的方法呼叫個別的更新和附加方法。

DbContext 變更追蹤方法︰ 加入

如果您使用 EF ObjectContext 過之 DbContext 的推出之前,您可能記得 ObjectContext 已加入、 附加和 Delete 方法。因為沒有辦法知道 ObjectSet 目標實體所屬的內容,您必須加入做為參數的 ObjectSet 名稱的字串表示。這因此似乎有點混亂,大多數人會發現只是為了使用 ObjectSet 加入、 附加和 Delete 方法比較簡單。DbContext 出現之後,當那些擠成一團方法不見了您只能加入、 附加和 DbSet 透過移除。

在 EF 核心、 加入、 附加和移除方法會回傳成 DbContext,以及更新的方法和四個相關 Range 方法 (AddRange 等等)。但是,這些方法現在都是很聰明。他們現在能夠判斷型別,以及自動與實體關聯至正確的 DbSet。這是很方便,因為它可讓您撰寫一般程式碼,而不需要具現化 DbSet。程式碼是更簡單,更重要、 更容易找到。以下是 EF6 和 EF 核心的比較︰

private void AddToSetEF6<T>(T entity) where T : class {Pull
  using (var context = new SamuraiContext()) {
    context.Set<T>().Add(entity);
  }
}
private void AddToSetEFCore(object entity) {
  using (var context = new SamuraiContext()) {
    context.Add(entity);
   }
}

Range 方法會更有幫助,因為您可以將各種型別傳入,EF 可以讓他們︰

private void AddViaContextEFCore(object[] entitiesOfVaryingTypes) {
  using (var context = new SamuraiContext()) {
     context.AddRange(entitiesOfVaryingTypes);
  }
}

DbContext.Entry: 修改 — 請注意這種行為變更

即使我們已經被警告 EF 核心不 EF6,我們不應預期的行為如同在 EF6 熟悉的程式碼,就仍然很難不這麼多的行為已被轉送時,會有這類的期望。DbContext.Entry 是一個例子,不過,並瞭解它已變更。

變更是我歡迎畫面的因為它可提供與變更追蹤的一致性。在 EF6,DbSet 中加入 (依此類推) 方法和 DbContext.Entry 方法結合 State 屬性具有相同的影響和圖形上的實體。因此,使用 DbContext.Entry(object)。State=EntityState.Added 會使所有的物件圖形 (也就尚未被追蹤) 中加入。

此外,發生永遠不會中斷連線之前將它們傳遞至變更追蹤程式的圖形物件以直覺的方式。

在 EF 核心 DbContext.Entry 現在會影響所傳遞的物件。如果該物件包含其他相關的物件連接到它,DbContext.Entry 將會忽略它們。

如果您經常使用的項目方法圖形連接至的 DbContext 執行個體,您可以看到為什麼這項變更是極端。這表示即使它是圖形的一部分,可以針對個別物件。

更重要的是,您現在可以明確地使用 DbSet 與 DbContext 追蹤方法 (Add,等等) 明確地使用圖形,您可以使用 DbContext.Entry 方法專門用於個別物件。加上我將說明,表示您現在需要清除的選項,以傳遞至 EF 核心物件圖形時,從選取變更追蹤器的下一個變更。

DbContext.ChangeTracker.TrackGraph: 加入

TrackGraph 是 EF 核心中全新的概念。它提供的終極控制您想要追蹤您的 DbContext 物件圖形中每個物件。

TrackGraph 將逐步引導圖形 (也就是它會逐一圖形中的每個物件) 和指定的函式套用到每一個這些物件。函式是 TrackGraph 方法的第二個參數。

最常見的範例就是每個物件的狀態設定為一般狀態。下列程式碼,TrackGraph 會逐一查看所有 newSword 圖形中的物件,並將其狀態設定為加入︰

context.ChangeTracker.TrackGraph(newSword, e => e.Entry.State = EntityState.Added);

TrackGraph 適用於為 DbSet 和 DbContext 方法同樣也要注意,如果實體已被追蹤,TrackGraph 會忽略它。雖然此特殊用途的 TrackGraph 的行為相同 DbSet 與 DbContext 追蹤方法,它並提供更多的機會,撰寫可重複使用程式碼︰

Lambda (在此程式碼為"e") 表示 EntityEntryGraphNode 型別。EntityEntryGraphNode 類型也會公開一個稱為 NodeType 屬性,您可能會遇到它透過 IntelliSense 的輸入 lambda。這似乎是供內部使用,並不會有效果 e.Entry.State 提供,因此請務必小心,不要將它不知情的狀況下。

在中斷連線的案例中,有關已追蹤的物件,忽略警告可能不相關。這是因為 DbContext 執行個體將新的和空的因此所有的圖形物件之 DbContext 的新手。不過,請考慮將圖形的集合傳遞到 Web API 的可能性。現在的通用物件的多個參考可能會與 EF 的變更追蹤程式會檢查以判斷是否已經追蹤實體的身分識別。這是理想的情況下,不能再次將物件加入的變更追蹤。

此預設行為被設計為滿足最常見的案例。但是我可以想像,像我一樣,您可能已經以為極端案例,這種模式可能會失敗。

這是我將 hearken 回到我 2016 年 3 月的文章,以及我針對您的類別上設定物件狀態,然後再讀取該狀態設定為指示變更 tracker 將物件的 EntityState 應該是共用的模式。現在我可以結合此模式,並讓函式 TrackGraph 呼叫 TrackGraph 方法執行的設定物件的狀態方法為基礎的 EntityState 工作。

在網域類別上的工作不會從我在 3 月發行項變更。我先定義本機追蹤 ObjectState 列舉︰

public enum ObjectState {
    Unchanged,
    Added,
    Modified,
    Deleted
  }

然後我建置公開 State 屬性,列舉為基礎的 IEntityObjectWithState 介面︰

public interface IObjectWithState
{
  ObjectState State { get; set; }
}

現在,我修正我的類別實作的介面。舉例來說,以下是就地介面與位置,從小型類別︰

using SamuraiTracker.Domain.Enums;
using SamuraiTracker.Domain.Interfaces;
namespace SamuraiTracker.Domain
{
  public class Location : IObjectWithState
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public ObjectState State { get; set; }
  }
}

在 3 月的文章中,我會示範如何建置智慧型能夠管理其本機狀態的類別。我還沒有重複,在此範例中,也就是說,在我的範例,我已保留 set 存取子為公用,就必須手動設定該狀態。在 fleshed 出方案中,我會加強這些類別中,有更多像之前的文章中的那些設定。

DbContext,我有一些靜態方法呼叫 ChangeTrackerHelpers,所示的 helper 類別 [圖 1

圖 1] ChangeTrackerHelpers 類別

public static class ChangeTrackerHelpers
    {
    public static void ConvertStateOfNode(EntityEntryGraphNode node) {
      IObjectWithState entity = (IObjectWithState)node.Entry.Entity;
      node.Entry.State = ConvertToEFState(entity.State);
    }
    private static EntityState ConvertToEFState(ObjectState objectState) {
      EntityState efState = EntityState.Unchanged;
      switch (objectState) {
        case ObjectState.Added:
          efState = EntityState.Added;
          break;
        case ObjectState.Modified:
          efState = EntityState.Modified;
          break;
        case ObjectState.Deleted:
          efState = EntityState.Deleted;
          break;
        case ObjectState.Unchanged:
          efState = EntityState.Unchanged;
          break;
      }
      return efState;
    }
  }

ConvertStateOfNode 是 TrackGraph 呼叫的方法。它將會設定物件的 EntityState 值取決於 ConvertToEFState 方法,這個值轉換為 IObjectWithState.State EntityState 值的方法。

有了這個地方,我現在可以使用 TrackGraph 以開始追蹤我的物件,以及其正確指派 EntityStates。以下是我傳遞呼叫 samurai,其中包含與相關的報價和劍 Samurai 物件圖形中的其中一個範例︰

context.ChangeTracker.TrackGraph(samurai, ChangeTrackerHelpers.ConvertStateOfNode);

在 EF6 方案中,我必須加入的變更追蹤的項目,接著明確呼叫的方法,會逐一查看所有的變更追蹤程式,設定每個物件的相關狀態中的項目。EF 核心解決方案會更有效率。請注意,我您尚未探索可能影響效能時處理大量的資料在單一交易中。

如果您下載本專欄的範例程式碼,您會看到我使用名為的 CanApplyStateViaChangeTracker 中我建立此圖形指派給不同物件的各種狀態,然後檢查結果的 EntityState 值正確的整合測試中的這個新模式。

IsKeySet: 加入

EntityEntry 物件,其中包含每個實體的追蹤資訊,有一個稱為 IsKeySet 的新屬性。IsKeySet 是另一項絕佳的應用程式開發介面。它會檢查實體中的索引鍵屬性是否具有值。這會排除猜測遊戲 (和相關程式碼) 的物件是否已經有其索引鍵屬性 (或如果您有包含索引鍵的屬性) 中的值。IsKeySet 會檢查值是否為指定的索引鍵屬性的特定類型的預設值。因此如果它是 int,為 0 嗎? 如果是 Guid,是等於 Guid.Empty (00000000-0000-0000-0000-000000000000)? 如果值不是類型的預設值,則 IsKeySet 會傳回 true。

如果您知道,在系統中您可以明確區分新的物件從已存在的物件其索引鍵屬性的值,IsKeySet 是很好用的屬性,判斷實體的狀態。

EF 核心眼睛寬開啟的狀態

因為 EF 小組確實完成它們可一直轉換您從舊版的 Entity Framework EF 核心的大腦,複寫大量的語法和行為,務必因此牢記這些是不同的 Api。移植程式碼將會需要一點技巧,建議您不要用 — 尤其是在這些初期 RTM 時已熟悉的功能子集。但是,即使您有信心的功能集 EF 核心具有您所需要做的新專案,不要假設事情的運作相同。這裡還有一點提醒自己。不過,我很喜歡 ChangeTracker 所做的變更。它們將比較明瞭、 較佳的一致性和更多的控制中斷連接的資料處理。

EF 團隊可以在 GitHub 頁面上,我建立了一個便捷的藍圖︰ bit.ly/efcoreroadmap。這可讓您追蹤功能,但它不會列出之類的行為變更的細節。為了,建議的測試,以確保正常您預期的方式很多的測試。如果您打算從舊版的 EF 移植程式碼,您可以查看 Llewellyn Falco 核准測試 (approvaltests.com),這可讓您比較測試,以確保輸出繼續比對的輸出。


Julie Lerman 是 Microsoft MVP,.NET 指導和居住在佛蒙特山區的顧問。 您可以找到她針對資料存取和使用者群組和世界各地的研討會其他.NET 主題呈現。她的部落格網址 thedatafarm.com /blog 以及 Code First DbContext 版本中的,所有從 O'Reilly Media 是 「 程式設計 Entity framework 」。在 Twitter 上追蹤她: @julielerman 並查看她 Pluralsight 課程 juliel.me/PS 影片

感謝以下的微軟技術專家對本文的審閱: Erik Ejlskov Jensen
Erik Ejlskov Jensen 是 NNIT A/S 和 Microsoft 資料平台 MVP 的.NET 和資料庫開發人員。他為.NET GitHub,包括受歡迎的 Visual Studio 擴充功能 」 SQLite 及 SQL Server Compact 工具箱 」 上的資料庫開發人員提供許多免費、 開放原始碼工具和程式庫。他也建立了 Entity Framework 核心的資料庫提供者。在 Twitter 上追蹤他︰ @ErikEJ .NET 資料存取開發的最新消息。