2017 年 9 月

第 32 卷,第 9 期

本文章是由機器翻譯。

資料點 - DDD-Friendlier EF Core 2.0

Julie Lerman

Julie Lerman如果您已被一段遵照這個資料行,您可能已注意到不少有關建置精益 Domain-Driven 設計 (DDD) 的模式和指引的方案時,實作 Entity Framework (EF) 的文件。即使 DDD 著重於網域,而不是在保存資料的方式,在某個時間點您需要資料流出您的軟體。

在過去的反覆項目的 EF,其圖樣 (傳統或自訂) 有允許使用者簡單的網域類別會直接對應到的資料庫沒有太多人事。和我指引通常已確認如果 EF 對應圖層會負責取得妥善設計的網域模型進出資料庫而不需要建立額外的資料模型,這便已足夠。但當您發現自己調整您的網域類別和邏輯,使其能更加配合 Entity Framework 的紅色旗標,它會建立以保存資料模型,然後將對應至資料模型類別的網域模型類別不同的時間點。

我的方式稍有驚訝要了解這些文件以了解 DDD 和 EF 對應多久以前來源。已經四年撰寫三段式序列稱為 「 網域導向設計撰寫程式碼:秘訣 Data-Focused 開發"橫跨年 8 月、 年 9 月和年 10 月的 2013 MSDN Magazine 的問題。以下是第一個部分,包含整個序列連結的連結: msdn.com/magazine/dn342868

發生之定址 DDD 模式,並且說明如何 EF 沒有或未輕鬆地將對應您的網域類別與資料庫之間兩個特定資料行。為準,EF6,其中一個最大的問題是事實,您無法用來封裝,並藉此保護 「 子 」 集合。使用已知的模式來保護集合 (通常這表示 em ploying IEnumerable) 未對齊 EF 的需求,和 EF 甚至不會辨識瀏覽應該是模型的一部分。Steve Smith 與我所花費大量時間考慮這個時候,我們建立我們 Pluralsight 課程 Domain-Driven 設計基本概念 (bit.ly/PS-DDD) 和最終 Steve 隨附不錯的因應措施 (bit.ly/2ufw89D)。

EF 核心最後會解決這個問題在 1.1 版和我撰寫此 2017 年 1 月的資料行中的新功能 (msdn.com/magazine/mt745093)。EF Core 1.0 和 1.1 版也會解決幾個其他 DDD 條件約束,但留一些間距,最值得注意的是無法對應所使用的網域類型 DDD 值物件。若要這樣做的功能已自其開始時,存在於 EF 但尚未以往已帶到 EF Core。但即將推出的 EF 核心 2.0,這項限制現在是消失。

我現在要執行本文中已配置的 EF 核心 2.0 功能可供您使用了許多 DDD 概念對齊。EF Core 2.0 的許多更容易使用的開發人員會運用這些概念,以及可能是它會為您介紹它們第一次。即使您不打算面向 DDD,仍然可以受益從其許多絕佳模式 ! 現在您可以使用 EF 核心會甚至多。

一對一取得更佳

在他的書籍 [Domain-Driven 設計] Eric Evans 指出,"雙向關聯表示這兩個物件可以是取消 derstood 只在一起。當兩個方向的周遊不要呼叫應用程式的需求時,加入周遊 di rection 可減少相依關係並簡化設計。 」 遵循本指南已確實會在我的程式碼中移除的副作用。EF 一直能夠處理單元方向-一對多和一對一的關聯性。事實上,在撰寫本文時,我已了解,與這兩個一對一關聯性結束需要的強制我 longtime 誤解您到雙向關聯性不正確。不過,您沒有明確設定這些必要的逛-tionships,而且這是您不需要立即執行的作業在 EF 核心,除了與邊緣案例。

一對一關聯性中 EF6 不適合需求已在相依類型的索引鍵內容必須同時做為外部索引鍵回到主要實體。這強制您設計類別奇數的方式,即使您有使用它。這點受惠引入的 EF 核心中的唯一外部索引鍵的支援,您現在可以有一對一的關聯性的相依端點中的明確外部索引鍵屬性。具有明確的外部索引鍵是更自然。而且在大部分情況下,EF 核心應該能夠正確推斷根據該外部索引鍵屬性存在的關聯性的相依端點。如果它不會找到正確因為某些邊緣案例,您必須加入稍後當我重新命名的外部索引鍵屬性時,我將示範組態。

若要示範一對一關聯性,我將會使用我最愛的 EF 核心網域: 電影"七個 Samu-rai 」 中的類別:

public class Samurai {
  public int Id { get; set; }
  public string Name { get; set; }
  public Entrance Entrance { get; set; }
}

public class Entrance {
  public int Id { get; set; }
  public string SceneName { get; set; }
  public int SamuraiId { get; set; }
}

現在使用 EF 核心,此類別組 — 日本武士和進入 (使用電影中字元的第一個出現)-會被正確識別為 uni 方向的一對一關聯性,以進入正在相依類型。不必在進入中包含的導覽屬性,並不需要任何特殊對應 Fluent API 中。外部索引鍵 (SamuraiId) 會依照慣例,因此 EF 核心無法辨識的關聯性。

EF 核心會推斷在資料庫中,Entrance.SamuraiId 是指回日本武士唯一外部索引鍵。請記住我喜歡的使用方式與因為的項目 (如我必須持續提醒自己),EF 核心不 EF6 ! 根據預設,.NET 和 EF 核心會將 Samurai.Entrance 視為選擇性屬性在執行階段除非您有網域邏輯中用來強制執行該進入是必要項。從開始 EF4.3,,您必須驗證就會回應至 [必要] 的註釋,對應的類別中的應用程式開發介面的好處。但是在 EF 核心監看的特定問題的任何驗證應用程式開發介面 (尚未?)。而且沒有其他需求的資料庫相關。例如,Entrance.SamuraiId 將會是不可為 null 的 int。如果您嘗試插入進入沒有 SamuraiId 值擴展,EF 核心不會攔截無效的資料,這也表示目前沒有抱怨 InMemory 提供者。但是,您的關聯式資料庫應該擲回錯誤,而條件約束衝突。

DDD 的觀點而言,不過,這並非真正問題因為您不應該依賴持續性圖層,以指出您網域的邏輯中的錯誤。如果日本武士需要進入,這是商務規則。如果您不能有或-phaned 進入,這也商務規則。因此,驗證應該仍是網域邏輯的一部分。

我在先前建議這些邊緣個案,以下是範例。如果在相依的實體 (例如,進入) 中的外部索引鍵不依照慣例,您可以使用 fluent 應用程式開發的應用程式開發介面,讓 EF 知道的核心。如果 Entrance.SamuraiId,或許 Entrance.SamuraiFK,您可以釐清該 FK 透過:

modelBuilder.Entity<Samurai>().HasOne(s=>s.Entrance)
  .WithOne().HasForeignKey<Entrance>(e=>e.SamuraiFK);

如果在兩端皆需要關聯性 (也就是進入必須有日本武士) 您可以加入 IsRequired WithOne 之後。

可以進一步封裝屬性

DDD 會引導您建立彙總 (物件圖形),其中彙總根 (在圖形中的主要物件) 是中的所有其他物件圖形中的控制項。這表示撰寫程式碼會防止其他程式碼誤用,或是甚至濫用規則。封裝屬性,因此它們無法隨機設定 (甚至是通常,隨機讀取) 是在保護圖表的主要方法。在 EF6 和舊版中,它們都是可以做出純量和導覽屬性有私用 setter,仍無法辨識的 EF 其讀取和更新的資料,但您無法輕鬆地進行屬性私用時。由 Rowan Miller 文章會示範其中一種做法在 EF6 和連結回某些舊版的因應措施 (bit.ly/2eHTm2t)。並沒有,則為 true 的方法,來保護瀏覽集合中的一對多關聯性。許多已寫入有關此第二個問題。現在,不僅可以輕鬆地您尚未 EF 核心處理私用屬性有支援欄位 (或推斷支援欄位),但您真正也可以將封裝的集合屬性,多虧了支援以 IEnumerable < T > 的對應。我撰寫相關的支援欄位和 IEnumerable < T > 我之前提到的 2017 年 1 月資料行中,所以我不 rehash 的詳細資料。不過,這是 DDD 模式非常重要,因此相關,請注意本文中。

雖然您可以隱藏純量和集合,還有一個其他類型的屬性,您很可能想要將封裝 — 導覽屬性。瀏覽集合受益於 IEnumerable < T > 支援,但導覽屬性,都是私用,例如 Samurai.Entrance、 無法理解模型。不過,是將模型設定為理解有隱藏的彙總根的導覽屬性。

例如,我可以在下列程式碼宣告進入來為日本武士的私用屬性 (和我甚至不能使用明確支援欄位,但可以視需要)。您可以建立新的入口 CreateEntrance 方法 (這會呼叫 factory 方法進入中),您可以只讀取進入 SceneName 屬性。請注意,我採用 C# 6 null 條件運算子,以避免例外狀況,如果我尚未尚未載入進入:

private Entrance Entrance {get;set;}
public void CreateEntrance (string sceneName) {
    Entrance = Entrance.Create (sceneName);
  }
public string EntranceScene => Entrance?.SceneName;

依照慣例,EF 核心不會進行此私用屬性的相關假設結果。即使複選支援欄位,不會自動探索到的私用入口,而您就無法與資料存放區互動時使用它。這是刻意設計的應用程式開發介面設計來幫助您防止可能的副作用。但是,您可以明確地設定。請記住,公用進入時,EF 核心是能夠瞭解的一對一關聯性。不過,因為它是私用您首先要確定 EF 了解這為止。

在 OnModelCreating,您需要加入讓 EF 核心知道 HasOne/WithOne fluent 應用程式開發的對應。進入是私人的因為您無法使用 lambda 運算式當做 HasOne 的參數。相反地,您必須依其型別和它的名稱屬性的描述。WithOne 通常要花費 lambda 運算式,以指定回配對的另一端的導覽屬性。但是,進入沒有日本武士導覽屬性,只要將外部索引鍵。很好 ! 因為 EF 核心現在有足夠的資訊來找出,您可以將參數保留空白:

modelBuilder.Entity<Samurai> ()
  .HasOne (typeof (Entrance), "Entrance").WithOne();

如果您使用支援屬性,例如 _entrance 日本武士類別中,這些變更中所示:

private Entrance _entrance;
private Entrance Entrance { get{return _entrance;} }
public void CreateEntrance (string sceneName) {
    _entrance = _entrance.Create (sceneName);
  }
public string EntranceScene => _entrance?.SceneName;

EF 核心會找出需要具體化進入屬性時,請使用支援欄位。這是因為很長的交談中所述的 Arthur Vickers 我們在 GitHub 上時已瞭解,如果"沒有支援欄位並沒有沒有 setter,EF 只是使用支援欄位 [,所以] 都可以使用"。 因此,它就會運作。

如果該支援欄位名稱並不依照慣例,如果比方說,您將它命名為 _foo,您需要的中繼資料 con figuration:

modelBuilder.Entity<Samurai> ()
  .Metadata
  .FindNavigation ("Entrance")
  .SetField("_foo");

現在會算出該關聯性無法更新資料庫與查詢。請記住,如果您想要使用積極式載入時,您必須使用 lambda 運算式; 無法探索進入 becaise 字串例如:

var samurai = context.Samurais.Include("Entrance").FirstOrDefault();

您可以使用標準語法所示的支援欄位文件頁面底部,與備份欄位篩選,之類的事情互動bit.ly/2wJeHQ7

現在支援值物件

值物件是 DDD 重要概念,因為它們允許您定義網域模型為實值類型。值 ob ject 沒有自己的身分識別,並成為實體使用它做為屬性的一部分。請考慮所組成的一系列的字元字串實值型別。變更單一字元,變更 word 的意義,因為字串是不可變的。若要變更的字串,您必須取代整個字串物件。DDD 會引導您考慮使用值物件任何地方找出一對一關聯性。您可以深入了解值 ob-jects DDD 基礎觀念課程先前所述。

EF 一律支援包含透過其 ComplexType 類型的值物件的能力。您無法定義型別沒有索引鍵,該型別為實體的屬性。這是觸發程序將其辨識為 ComplexType,並將其屬性對應到資料表實體所對應的 EF 足以。然後,您無法擴充的型別也有 re-quired 值物件,例如確保類型的不可變且方法來評估每個屬性,判斷相等時,並覆寫的雜湊的功能。我經常會衍生自 Jimmy Bogard ValueObject 基底類別,以快速採用這些屬性的我的型別。

人員的名稱會是常用來當做值物件的類型。您可以確保的每當有人想要讓某個人的名稱在實體中,它們永遠會接一組常用的規則。圖 1示範具有第一個和最後一個屬性的簡單 PersonName 類別 — 兩者都完全封裝,以及屬性來傳回 FullName。類別的設計可確保一律會提供兩個部分的名稱。

圖 1 PersonName 值物件

public class PersonName : ValueObject<PersonName> {
  public static PersonName Create (string first, string last) {
    return new PersonName (first, last);
  }
  private PersonName () { } 
  private PersonName (string first, string last) {
    First = first;
    Last = last;
  }
  public string First { get; private set; }
  public string Last { get; private set; }
  public string FullName => First + " " + Last;
}

我可以使用 PersonName 中其他類型的屬性,並繼續來充實 PersonName 類別中的其他邏輯。透過為一對一的關聯性之值物件的優點是,我不必維護關聯性,我撰寫程式碼時。這是標準的物件導向程式設計。它是只是另一個屬性。在日本武士類別中,已新增做它的 setter 私人並提供另一個名為識別使用而不是 setter 方法,此類型的新屬性:

 

public PersonName SecretIdentity{get;private set;}
public void Identify (string first, string last) {
  SecretIdentity = PersonName.Create (first, last);
}

EF 核心 2.0 中,直到時發生沒有的功能類似參考的 ComplexTypes,因此您無法輕鬆地將值物件,而新增 ing 不使用個別的資料模型中。而不是只重新實作在 EF 核心 ComplexType,EF 小組建立呼叫的概念擁有實體,其會利用另一個的 EF 核心功能,遮蔽屬性。現在,擁有的實體識別為其他類型的屬性,自己 EF 核心以及了解它們如何解決資料庫結構描述中,以及如何建立查詢和遵守該資料的更新。

EF 核心 2.0 慣例將不會自動探索此新 SecretIdentity 屬性是要併入保存的資料類型。您必須明確告知 DbContext Samurai.SecretIdentity 屬性所擁有的實體 DbContext.OnModelCreating 使用 OwnsOne 方法中:

protected override void OnModelCreating (ModelBuilder modelBuilder) {
  modelBuilder.Entity<Samurai>().OwnsOne(s => s.SecretIdentity);
}

這會強制 PersonName 解析為日本武士屬性的屬性。在您的程式碼會使用 Samurai.SecretIdentity 型別,並瀏覽的第一個和最後一個屬性,這兩個屬性會解析為 Samurais 資料庫資料表中的資料行。EF 核心慣例會加以命名日本武士 (SecretIdentity) 中的屬性名稱和擁有的實體屬性的名稱中所示圖 2

Samurais 資料表,其中包含值的屬性結構描述
圖 2 Samurais 資料表,其中包含值的屬性結構描述

現在我可以識別日本武士密碼名稱,然後將它儲存以程式碼如下所示:

using (var context = new SamuraiContext()) {
  var samurai = new Samurai { Name = "HubbieSan" 
  samurai.Identify ("Late", "Todinner");
  context.Samurais.Add (samurai);
  context.SaveChanges ();
}

在資料存放區中,"晚期 「 取得保存 SecretIdentity_First 欄位及 「 Todinner"SecretIdentity_Last 欄位。

然後可以直接查詢日本武士:

var samurai=context.Samurais .FirstOrDefaultAsync (s => s.Name == "HubbieSan")

EF 核心可確保會填入產生日本武士 SecretIdentity 屬性,以及我可以看見要求身分識別:

samurai.SecretIdentity.FullName

EF 核心需要擁有實體的屬性會填入。下載範例中,您會看到如何設計為可容納該 PersonName 型別。

簡單的課程簡單類別

我已顯示的內容如下的最小的方式,可讓您利用一些 DDD 實作的核心概念的簡單類別,請參閱 EF 核心如何回應這些建構。您已看過 EF 核心 2.0 是能夠了解一對一 uni 方向的關聯性。它可以保存資料從實體完整封裝純量、 導覽和集合的屬性。它也可讓您使用網域模型中的值物件,並保存,也可以。

這個發行項,我已保留類別簡單以及缺乏更正確地限制的實體及使用 DDD 模式的值物件的其他邏輯。這種會反映在下載範例中,這也是在 GitHub 上bit.ly/2tDRXwi。那里您可以在其中找到簡單版和進階的分支我已在此再提高關閉這個網域模型,並套用至彙總根 (日本武士)、 其相關的實體 (進入) 和值物件 (Directive) 的一些其他 DDD 作法,讓您可以查看如何EF 核心 2.0 處理 DDD 彙總的比較實際的運算式。在即將發行的資料行,我將討論套用在該分支中的進階的模式。

請記住,我使用的 EF 核心 2.0 儘速在最終發行前版本。雖然大部分的 [我已配置的行為實線,仍會有做一些調整的可能性 2.0.0 釋放之前。


Julie Lerman Microsoft 地區主管、 Microsoft MVP、 軟體小組指導,以及位於 Vermont 山區顧問。您可以找到其呈現在資料存取和使用者群組和所做的心得世界各地的其他主題。在她部落格thedatafarm.com/blog和以及 Code First DbContext 版本中的,所有從 O'Reilly Media 是"程式設計 Entity Framework"的作者。在 Twitter 上關注她: @julielerman ,請參閱在她 Pluralsight 課程juliel.me/PS 影片


MSDN Magazine 論壇中的這篇文章的討論