本文章是由機器翻譯。

資料點

朱莉列爾曼

Entity Framework(EF) 已經存在了八年多,經歷了很大的變化。這意味著開發人員正在尋找使用 EF 的早期版本,請參閱如何他們可以受益于其較新的增強功能的應用程式。我共事的經歷這一過程的軟體團隊數目和我分享一些經驗教訓和指導在兩部分的系列中。上個月我討論了更新到 EF6,使用舊的 EF4 應用程式作為一個例子 (bit.ly/1jqu5yQ)。我還提供提示為爆發規模較小的實體資料模型,從大型模型 (EDMX 或代碼第一次),因為我推薦使用多個重點型號,而不是使用一個大型的模型 — — 滿載併發症的常常是不必要的關係 — — 整個應用程式。

這個月,我會使用一個具體的例子來共用有關打破了一個小的模型,更多詳細資訊,然後,用那小的模型,通過從 ObjectCoNtext API 切換到較新的 DbCoNtext API 時遇到的問題的一些工作。

對於這篇文章中,我使用一個現有的應用程式,以目前的現實世界重構您自己的解決方案時,您可能會遇到的問題是有點超過典型的演示應用程式。它是從我的書,"程式設計Entity Framework,第 2 版"的小應用程式範例 (O'Reilly 媒體,2010年),使用 EF4 和 ObjectCoNtext API 編寫的。其 EDMX 模型是使用生成的資料庫第一次,然後在 EF 設計器中自訂。在此解決方案中,已經從預設 T4 代碼生成範本創建實體類,所有從 EntityObject 類繼承。相反,我用一個 T4 範本,創建平原舊 CLR 物件 (思卡爾),支援以 EF4 開頭,並且我已經有點自訂範本。我應該注意這是一個用戶端的應用程式,因此,不會有一些挑戰的一個斷開連接的應用程式,狀態跟蹤在哪裡,更具挑戰性。不管怎麼說,你會在這裡看到的問題的許多適用于這兩種方案。

在上個月的專欄中,我升級這種解決方案使用 EF6 和微軟.NET Framework 4.5,但我並沒有對我的代碼庫進行任何更改。它仍在使用原始的 T4 範本生成 POCO 類和 ObjectCoNtext 類,用於管理的持久性。

應用程式的前端為其 UI 使用Windows Presentation Foundation(WPF)。雖然該體系結構階層式從那時起絕對進化我上構建應用程式的想法。不過,我會避免全面爆發的重構,使我的心景時我看了看更新的代碼。

我為此列的目標是:

  • 提取一個較小的模型,專注于一項任務。
  • 更改代碼生成的輸出較新風格 POCO 類和一個 DbCoNtext 類來管理持久性更小的模型。
  • 修復任何現有的代碼,它使用 ObjectCoNtext 並打破了由於交換器到 DbCoNtext。在許多情況下,這意味著創建重複的方法,然後修改那些對 DbCoNtext 邏輯。這種方式,我不會打破仍然正在使用的原始模型和其 ObjectCoNtext 的其餘代碼。
  • 尋找機會,用更簡單、 更高效的功能介紹 EF5 和 EF6 替換邏輯。

創造之旅維修模型

在上個月的專欄中,我提供了識別和提取一個較小的模型從一個大的指標。以下的指引,我檢查了這個應用程式,這並不是很大,但確實包含用於管理各種任務所涉及的運行冒險旅行業務實體的模型。它具有維持旅行目的地、 活動、 住宿、 日期和其他詳細資訊,以及客戶、 他們的保留、 付款和雜物的聯繫資訊由定義的實體。你可以看到完整的模型在左邊的圖 1


圖 1 在左邊,右邊,約束模型完整的模型創建使用紅色大模型中所示的實體

一個應用程式的任務允許使用者定義新的旅行,以及維持現有旅行使用 WPF 的形式,如中所示圖 2


圖 2Windows Presentation Foundation的形式來管理旅行的詳細資訊

WPF 表單的處理就是旅行的定義:選擇一個目的地和酒店、 開始日期和結束日期和各種各樣的活動。因此,我可以想像限於滿足只是這一組的任務模型。我創建了一個新的模型 (也顯示在圖 1) 使用Entity Data Model嚮導並選擇只將相關的表。該模型也是負責的事件和活動之間的多對多關係的聯合資料表的意識到的。

接下來,我申請 (我的代碼所依賴的) 的同一自訂這些實體在我我以前曾在原始模型中定義的新模型。其中多數的名稱更改為實體和它們的屬性 ; 例如,事件成為了行程和位置成為了目的地。

我正與 EDMX,但可以通過創建一個新的 DbCoNtext 類目標只有四個實體為代碼第一次定義一個新的模型。在這種情況下,然而,你需要到任一定義新類型不包含多餘的關係或使用 Fluent API 映射將忽略特定的關係。我在 2013 年 1 月資料點專欄,"收縮模型與 DDD 有界語境"(bit.ly/1isIoGE),為代碼第一個路徑提供了指導。

很高興與維持的關係,在這方面,只是不關心的挑戰去做。綁在示例,保留,和所有的東西 (如付款和客戶),保留就是現在走了。

我也修剪住宿和活動,因為我真的需要他們的身份和名稱僅為旅行維修。不幸的是,一些周圍到非可以為 null 的資料庫列的映射規則意味著我不得不保留客戶 id 和 DestinationID。但我不再需要導覽屬性,從目的地或倒伏回旅行,所以我他們從模型中刪除。

下一步,我不得不考慮怎麼辦用代碼生成的類。我已經有了其他的模型,從生成的類,但這些類涉及到我不需要 (或已經) 的關係在這個模型中。因為我的注意力一直在領域驅動設計 (DDD) 數年,我很舒服,有一組類特定于這個新的模型和使用它們分別從其他生成的類。這些類都在一個單獨的專案和不同的命名空間。因為它們映射到一個共同的資料庫,我不必擔心在沒有出現在另一個地方所做的更改。上課只上將重點放在維護智慧財產權的任務將使編碼更簡單,儘管這意味著一些重複穿過我的解決方案。冗余是我願意做一個權衡,我來到了術語與已經在以前的專案。左側的圖 3 顯示的範本 (BreakAway.tt) 和生成的類與大型模型相關聯。他們在他們自己的專案,BreakAwayEntities。我會解釋的右端的圖 3 不久。


圖 3 對於大型模型,從 T4 範本生成的原始類相比,新模型和其生成的類

根據上個月的專欄中,當我創建了新的模式,我用我原來的代碼生成範本以便在此步驟中我唯一的挑戰將是確保我使用小的模型,而不必同時擔心改變了 EF Api 的應用程式功能。能夠做到這通過替換預設範本檔 (TripMaintenance.CoNtext.tt 和 TripMaintenance.tt) 中的代碼用在我的 BreakAwayEntities.tt 檔的代碼了此外,我不得不修改範本代碼,使其指向新的 EDMX 檔中的檔路徑。

在所有,我花了大約一個小時的更改引用和命名空間,直到能運行我的小應用程式和我的測試中再次使用的新模式 — — 不只是檢索資料,但編輯和插入新資料圖。

拔掉插頭:移動到 DbCoNtext API

現在我有一個更小的模型,都少得多複雜,而會使與此我簡單的 WPF 代碼中的資料進行交互的任務。我準備攻擊更難苦差事:我用 DbCoNtext 替換我的 ObjectCoNtext,所以可以刪除的代碼寫了,彌補了直接與 ObjectCoNtext 一起工作的複雜性。我很感謝我會糾纏只與較小的表面區域的與我的新模型相關的代碼。這就像再次破斷了的胳膊,所以它會設置正確,而其餘的我的應用程式的骨頭將保持不變、 無痛苦的。

為我 EDMX 更新代碼生成範本我會繼續使用 EDMX,所以要想切換到 DbCoNtext API 之旅維修,我必須選擇一個新的代碼生成範本。我可以輕鬆地訪問的一個我想:EF 6.x DbCoNtext 發電機組,因為它與Visual Studio2013年安裝。第一,我會馬上刪除附加到 TripMaintenance.EDMX 檔中,兩個 TT 檔,然後可以使用設計器的添加代碼生成項工具來選擇新的範本。(如果你使用代碼生成範本不熟悉,在看到 MSDN 文檔中,"EF 設計器代碼生成範本," bit.ly/1i7zU3Y)。

記得我定制了原來的範本。我將需要在新範本中複製一個臨界自訂項去除注入虛擬關鍵字上導覽屬性的代碼。這將確保不會觸發延遲載入,因為我的代碼取決於這種行為。如果您使用的 T4 範本,自訂任何東西為您原始的模型,這是非常重要的一步,要牢記心中。(您可以查看 msdn 的演示編輯在 EDMX T4 範本創建一個舊視頻 bit.ly/1jKg4jB.)

最後一個細節是參加到一些我曾為一些實體和 ObjectCoNtext 創建的分部類。我複製到新的模型專案相關的確信它們是被綁的新生成的類。

修復 AddObject、 AttachObject 和 DeleteObject 方法現在是時候來看到的損害。儘管很多我破碎到我如何編碼 ObjectCoNtext API 針對特定,那裡是代碼的一套容易成為攻擊目標的方法,將是代碼的常見的大多數應用程式,所以要我揍那第一。

ObjectCoNtext API 提供了多種方法來添加、 附加和從 ObjectSet 中刪除物件。例如,要添加一個專案如命名為 newTrip,旅行的一個實例可以直接使用 ObjectCoNtext:

context.AddObject("Trips",newTrip)

或者你可以從 ObjectSet:

_context.Trips.AddObject(newTrip)

ObjectCoNtext 方法是有點笨拙,因為它需要一個字串來標識實體屬於哪一套。 ObjectSet 方法較為簡單,因為已經定義了一套,但它仍然使用笨重的術語:AddObject、 AttachObject 和 DeleteObject。 與 DbCoNtext,有只是一種方式 — — 通過 DbSet。 方法名稱被簡化為 Add,附加和刪除更好地模仿集合的方法,例如:

_context.Trips.Add(newTrip);

DeleteObject 成為了刪除的通知。 這可能是方法的令人困惑的因為同時刪除與收藏更好地對齊,它並不真的狀態,就是方法的最終從資料庫中刪除該記錄的意圖。 我見過開發商錯誤地假設 Collection.Remove 將有 DbSet.Remove—indicating 到 EF 從資料庫中刪除的目標實體得到相同的結果。 但它不會。 所以要小心的。

其餘的我會解決的問題是特定于我的 ObjectCoNtext 在中如何使用我原來的應用程式。 他們不一定相同的問題,你將會遇到,但看到這些特定的中斷 — — 以及如何解決這些問題 — — 將説明您準備不論你遇到時,切換到 DbCoNtext 在您自己的解決方案。

修復中上下文的自訂的分部類的代碼我開始我修復了通過建立包含我的新模型的專案。 一旦這個專案解決了,我可以攻擊依賴于它的專案。 我最初創建擴展的 ObjectCoNtext 的分部類是第一次失敗。

在分部類中的自訂方法之一是 ManagedEntities,幫我看看什麼實體由上下文被跟蹤。 ManagedEntities 依賴我創建了一個擴充方法:GetObjectStateEntries 一個無參數的重載。 我使用該重載是編譯器錯誤的原因:

public IEnumerable<T> ManagedEntities<T>() {
  var oses = ObjectStateManager.GetObjectStateEntries();
  return oses.Where(entry => entry.Entity is T)
             .Select(entry => (T)entry.Entity);
 }

而不是固定的底層的 GetObjectStateEntries 擴充方法,我更可以只是消除它和 ManagedEntities 方法因為 DbCoNtext API 在調用的本地我可以用來代替的 DbSet 類上有一種方法。

有兩種方法,我可以帶到此重構。 一個是代碼的所有使用調用 ManagedEntities 我新模型使用查找和替換它的 DbSet.Local 方法。 這裡是代碼的一個使用 ManagedEntities 來遍歷所有的上下文跟蹤的車次的示例:

foreach (var trip in _context.ManagedEntities<Trip>())

我可以取代那與:

foreach (var trip in _context.Trips.Local.ToList())

請注意本地返回 ObservableCollection,所以我添加一一列舉實體中提取的。

或者,如果我的代碼具有許多調用 ManagedEntities,我只是能改變 ManagedEntities 背後的邏輯,避免不必編輯所有的地方,它用。 因為方法是泛型方法,它不是簡單地使用旅行 DbSet,一樣簡單,但變化是仍然足夠簡單:

public IEnumerable<T> ManagedEntities<T>() where T : class  {
  return Set<T>().Local.ToList();
}

最重要的是,我的旅行管理邏輯是不再根據重載的 GetObjectStateEntries 擴充方法。 我能離開那個方法完整,這樣,我原來的 ObjectCoNtext 可以繼續使用它。

長遠來說,我不得不使用擴充方法來執行的技巧很多人變得不必要使用 DbCoNtext API 時。 他們是這種通常需要的模式 DbCoNtext API 包括簡單的方式,像是本地方法,來執行它們。

我發現在分部類中的下一個破碎的方法是一個我用來將一個實體的跟蹤的狀態設置為已修改。 再次,我被迫使用一些非顯而易見的 EF 代碼做的事。 失敗行是代碼的:

ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);

我可以用更簡單的 DbCoNtext.Entry() 替換這。國家財產。 因為這段代碼是在擴展我 DbCoNtext 的分部類中,我可以訪問入口方法,直接而不是從 TripMaintenanceCoNtext 的一個實例。 新行是代碼的:

Entry(entity).State = EntityState.Modified;

我的分部類中的最後的方法 — — 也破 — — 使用 ObjectSet.ApplyCurrentValues 方法來修復使用的相同類型的另一個 (跟蹤) 實例值跟蹤實體的狀態。 ApplyCurrentValues 使用的標識值的實例你傳遞中,在更改跟蹤器中查找匹配的實體,然後更新它使用傳入的物件的值。

在 DbCoNtext 到 ApplyCurrentValues 還有沒有等效。 Db­上下文允許你做類似的值替換使用 Entry()。CurrentValues()。集,但這需要您已經對跟蹤實體的訪問。 沒有容易的方式來生成泛型方法,發現跟蹤實體來替換該功能。 但是,所有不會丟失。 你可以只繼續用特別的 ApplyCurrentValues 方法所利用的能力要從 DbCoNtext 訪問 ObjectCoNtext 邏輯。 記住,DbCoNtext 是 ObjectCoNtext 和 API 的包裝器提供一種方法,可以深入瞭解底層的 ObjectCoNtext 的特殊情況,例如 IObjectCoNtextAdapter。 我添加一個簡單的屬性,核心,到我的分部類,可以很容易地重用:

public ObjectContext Core {
  get {
    return (this as IObjectContextAdapter).ObjectContext;
  }}

然後修改後我要繼續使用 ApplyCurrentValues,從核心屬性調用 CreateObjectSet 的分部類的相關方法。 這給了我的 ObjectSet,所以我可以繼續使用 ApplyCurrentValues:

public void UpdateEntity<T>(T modifiedEntity) where T : class {
  var set = Core.CreateObjectSet<T>();
  set.ApplyCurrentValues(modifiedEntity);
}

最後一項改變,我的模型專案是能夠編譯。 現在是時間來處理斷代碼在我的 UI 和模型之間的層數。

MergeOption.NoTracking 到 AsNoTracking 查詢和 lambda 時,太指定 EF 不應跟蹤結果是重要的方式,以避免不必要的處理和提高性能。 有在我的應用程式中的多個地方在那裡我查詢將僅作為一個參考清單的資料。 這裡是一個例子,在哪裡檢索唯讀訪問以及他們的目的地資訊,在我的使用者介面上的清單方塊中顯示清單。 與舊的 API,您必須設置 MergeOption 的查詢執行它之前。 這裡是我不得不寫的醜陋的代碼:

var query = _context.Trips.Include("Destination");
query.MergeOption = MergeOption.NoTracking;
_trips = query.OrderBy(t=>t.Destination.Name).ToList();

DbSet 有更簡單的方法來使用其 AsNoTracking 方法執行此操作。 當我站在這裡時,我可以擺脫在包括方法中,字串的 DbCoNtext 最後添加那裡使用 lambda 運算式的能力。 下列為改寫的程式碼:

 

_trips= _context.Trips.AsNoTracking().Include(t=>t.Destination)
        .OrderBy(t => t.Destination.Name)
        .ToList();

到救援再 DbSet.Local 很多地方在我的代碼所需的我發現是否一個實體被跟蹤時我的是它的標識值。 我寫了一個説明器方法,所示圖 4,為做到這一點,,你可以看到我為什麼想要將此代碼封裝。 Don了,不要打擾試圖破譯它 ; 它被領導垃圾鬥。

圖 4 補丁説明我的方法變得不必要感謝 DbSet.Local 的新方法

public static bool IsTracked<TEntity>(this ObjectContext context,
  Expression<Func<TEntity, object>> keyProperty, int keyId)
  where TEntity : class
{
  var keyPropertyName =
    ((keyProperty.Body as UnaryExpression)
    .Operand as MemberExpression).Member.Name;
  var set = context.CreateObjectSet<TEntity>();
  var entitySetName = set.EntitySet.EntityContainer.Name + 
    "." + set.EntitySet.Name;
  var key = new EntityKey(entitySetName, keyPropertyName, keyId);
  ObjectStateEntry ose;
  if (context.ObjectStateManager.TryGetObjectStateEntry(key, out ose))
  {
    return true;
  }
  return false;
}

這裡是代碼的我叫補丁的值傳遞的一次旅行,我想要找的應用程式中示例:

_context.IsTracked<Trip>(t => t.TripID, tripId)

由於早期我使用相同的 DbSet.Local 方法,我是能夠取代那與:

_context.Trips.Local.Any(d => d.TripID == tripId))

然後我又能夠刪除補丁方法 ! 是你跟蹤的多少代碼我已經能夠刪除為止嗎?

我寫了為應用程式,AddActivity,不僅僅只是驗證是否已經被跟蹤的實體所需的另一種方法。 它需要得到該實體 — — 另一項任務地方能説明我。 AddActivity 方法 (圖 5) 將活動添加到特定的旅行使用醜陋和非顯而易見我不得不的 ObjectCoNtext API 編寫的代碼。 這涉及到旅行和活動之間的多對多關係。 附加活動實例正在跟蹤一趟導致 EF 開始跟蹤活動,所以我需要保護上下文從一個重複 — — 和我的應用程式從產生的異常。 在我的方法,我試圖檢索實體的 ObjectStateEntry。 TryGetObjectStateEntry 在一次執行兩個技巧。 第一,它返回一個布林值,如果該條目是發現的其次,返回的發現的進入或 null。 如果該條目不是空的我用其實體將附加到行了 ; 否則,我附上了一個傳遞到我的 AddActivity 方法。 只描述,是使人筋疲力盡的。

圖 5 使用 ObjectCoNtext API 的由來 AddActivity 方法

public void AddActivity(Activity activity)
{
  if (_context.GetEntityState(activity) == EntityState.Detached)
  {
    ObjectStateEntry existingOse;
    if (_context.ObjectStateManager
        .TryGetObjectStateEntry(activity, out existingOse))
    {
      activity = existingOse.Entity as Activity;
    }
    else     {
      _context.Activities.Attach(activity);
     }
  }
  _currentTrip.Activities.Add(activity);
}

我很久,並且想到高效的方式來執行這種邏輯但以大致相同的長度的代碼而告終。 記住,這是一種多對多關係,它們需要更多的關懷。 然而,該代碼是寫更容易和更易閱讀。 你不必糾結與 ObjectStateManager 在所有。 這裡是方法的我更新,以使用類似的模式,像與目的地做早些時候的那樣的一部分:

var trackedActivity=_context.Activities.Local
    .FirstOrDefault(a => a.ActivityID == activity.ActivityID);
if (trackedActivity != null) {
  activity = trackedActivity;
}
else {
  _context.Activities.Attach(activity);
}

任務完成

與此的最後解決,所有我的測試通過,我能夠成功地使用所有的旅行維護表單的功能。 現在它是尋找機會,以充分利用新 EF6 功能的時間。

更重要的是,任何進一步的維護上這部分的我的應用程式將會簡化,因為很難與 ObjectCoNtext 的許多工都是與 DbCoNtext API 容易得多。 專注于這個小模型,我學會了很多,我可以利用任何其他 ObjectCoNtext 到 DbCoNtext 轉換,我更好地準備未來。

同樣重要的是要聰明地選擇應更新什麼代碼,什麼應由孤獨。 我通常目標我知道我將不得不在未來維持,不想也不用擔心與更難用的 API 進行交互的功能。 如果你有代碼,將不會再感動並繼續工作隨著 EF 的演變,我一定會想投身于這一挑戰之前兩倍。 即使它是最好的斷了的胳膊可以是相當痛苦的。  

Julie Lerman 是 Microsoft MVP,.NET 的導師和顧問,住在佛蒙特州的山。你可以找到她提出關於資料訪問和 other.NET 的使用者組和世界各地的會議主題。她的博客 thedatafarm.com/blog 和的作者是"程式設計Entity Framework"(2010 年) 以及代碼第一版 (2011 年) 和 DbCoNtext 版 (2012 年),所有從 O'Reilly 媒體。跟著她在 Twitter 上 twitter.com/julielerman ,看看她的 Pluralsight 課程 juliel.me/PS-視頻

感謝以下的微軟技術專家對本文的審閱:安德魯 · 奧克利