2017 年 10 月

第 32 卷,第 10 期

本文章是由機器翻譯。

資料點 - DDD-Friendlier EF Core 2.0,第 2 部

Julie Lerman

Julie Lerman我年 9 月資料行中 (msdn.com/magazine/mt842503),我配置許多的 Entity Framework Core (EF 核心) 2.0 的 fea tures 的整齊 Domain-Driven 設計 (DDD) 原則。除了軟體開發提供絕佳的指引和模式,也是如果您在設計 microservices DDD 原則。在整個本文範例中,我可以使用簡單模式以便專注於特定的 EF 核心功能。如此一來表示程式碼未代表設計完善 DDD 引導式的類別,以及我承諾,即將發行的資料行,我會發展中這些類別起來更像您可能會撰寫使用 DDD 的實際實作。而這是我現在要執行本文中。我要逐步引導您完成這些不懷好意架構的類別,並說明它們如何繼續運作也因為使用 EF 核心 2.0 以對應至我的資料庫。

原始網域模型

我一開始會使用快速複習我很少的網域模型上。由於範例中,網域缺少通常會驅動您上 DDD,了解複雜的商務問題,但即使沒有這些複雜的問題,我仍然可以套用模式讓您可以看到它們的動作,並查看這些回應 EF 核心 2.0 的方式。

網域包含日本武士字元從影片"七個日本武士",其中我追蹤的第一個功用的影片,其密碼的身分識別。

在原始的文章中,日本武士彙總根,我有限制的模型,以確保日本武士是負責管理其進入和其密碼的身分識別。我所示範部分這些限制,如下所示:

日本武士和進入有一對一的關聯性。日本武士的進入欄位為私用。進入有外部索引鍵欄位,SamuraiId。因為 Samurai.Entrance 是私人的我需要 DbContext 類別,以確保 EF 核心無法理解擷取和保存這項資料的關聯性中加入 fluent 應用程式開發的應用程式開發介面對應。我發展進入屬性繫結至支援欄位,並再修改,讓這個問題,也知道 EF 核心的對應。

PersonName_ValueObject (名為因此 elaborately 供您參考) 是它自己的身分識別不是值的物件類型。它可用來當作中其他類型的屬性。日本武士具有呼叫 SecretIdentity PersonName_ValueObject 屬性。我用於新的 EF 核心擁有實體功能讓 SamuraiContext 知道要視為 SecretIdentity 相同如同舊版的 EF 一樣處理 ComplexType 的日本武士類型會對應到相同資料表的資料行中儲存之值物件的屬性。

增強的網域模型

下面這樣會更進階的類別在彙總,以及用於對應到資料庫,而在這個案例中是 SQLite EF 核心 2.0 DbContext。在圖表圖 1顯示其類別詳細資料與彙總。程式碼清單將會啟動具有非根實體,並完成的根目錄中,控制其他日本武士。請注意,我已經移除命名空間的參考,您可以下載本文章中會看到它們。

 

進階的彙總的圖表

圖 1 的進階的彙總的圖表

圖 2示範進化的進入類別。

圖 2 進入類別設計遵循 DDD 模式

public class Entrance {
  public Entrance (Guid samuraiGuidId,int movieMinute, string sceneName, string description) {
    MovieMinute = movieMinute;
    SceneName = sceneName;
    ActionDescription = description;
    SamuraiGuidId=samuraiGuidId;
  }
  private Entrance () { } // Needed by ORM
  public int Id { get; private set; }
  public int MovieMinute { get; private set; }
  public string SceneName { get; private set; }
  public string ActionDescription { get; private set; }

  private int SamuraiFk { get;  set; }
  public Guid SamuraiGuidId{get;private set;}
}

DDD 程式碼,大部分都需保護您的網域不小心被誤用或濫用。您要限制的存取權來確保只有在您想要的方式中所使用的類別內的邏輯。我打算朱冰 En 封術類別 (圖 1) 是不變。您可以定義多載的建構函式,傳遞的值及其所有屬性除了 SamuraiFk 其屬性值。您能讀取的任何屬性,不過請注意,它們具有私用 setter。建構函式是會影響這些值的唯一方式。因此,如果您需要修改它,您必須取代的全新進入執行個體。此類別看起來像候選值物件時,尤其是因為它是不可變的但我想要使用示範 EF 核心中有一對一的行為。

使用 EF 核心 (和 EF 的早期反覆項目),當您查詢資料,EF 便能具體化結果,即使屬性具有私用 setter,因為它會使用反映。因此 EF 核心可以使用所有這些屬性進入具有私用 setter。

沒有來填入屬性進入的四個參數的公用建構函式。(在上一個範例中,我使用這個類別中,加入任何值,所以我已將它移除這個反覆項目中的 factory 方法)。 在這個網域中,進入與任何遺漏的屬性可讓沒有意義,所以我正在限制它的設計,若要避免。下列的建構函式是私用的無參數建構函式。因為 EF 核心和 EF 使用反映來具體化結果,如其他具現化物件,例如 JSON.NET 的 Api 它需要的無參數建構函式,可供使用。第一個建構函式會覆寫參數的建構函式所提供的所有類別都衍生自的基底類別 (物件)。因此,您必須明確將此加回去。這不是以 EF 核心; 新的行為它是如何處理 EF 長時間存取過的項目。在本文的內容,不過,它具有重複。如果您是以 EF 與此版本新,也很值得,進入建立時為查詢的結果,EF 核心只會使用該參數的建構函式來建立物件。公用建構函式可用於建立新的入口物件。

關於該 Guid 和 int 指回日本武士什麼?網域使用的 Guid 連接日本武士和進入,使網域邏輯具有不可仰賴其識別碼的資料存放區。SamuraiFk 只將用於每個 sistence。SamuraiFk 是私人的但 EF 核心能夠推斷其的支援欄位。如果它被命名為 SamuraiId,EF 核心會將其辨識為外部索引鍵,但因為它不會依照慣例,會讓回,事實上,外部索引鍵的 EF 核心內容中的特殊對應。它是私用的原因是它不是適用於網域,但需要理解的關聯性,才能儲存和擷取資料,正確的 EF 核心。這是來說,若要避免在我的網域類別,但在我的意見,次要的一不對齊的簡介和維護完全不同的資料模型的額外工作,持續性邏輯。

我的彙總中沒有新的實體:引號、 示圖 3。此範例網域可接受的電影各種字元會有一些值得注意的引號我想要追蹤的這個網域中。它也可以讓我示範一對多關聯性。

圖 3 遵循 DDD 模式而設計的引號類型

public class Quote {
  public Quote (Guid samuraiGuidId,string text) {
    Text = text;
    SamuraiGuidId=samuraiGuidId;
  }
  private Quote () { } //ORM requires parameterless ctor
  public int Id { get; private set; }
  public string Text { get; private set; }
  private int SamuraiId { get; set; }
  public Guid SamuraiGuidId{get;private set;}
}

請注意,模式都一樣也說明了進入實體: 多載的公用建構函式和私用的無參數建構函式、 私用 setter、 持續性,私用外部索引鍵的屬性和 Guid。唯一的差別在於,SamuraiId,做為持續性 FK,如下所示 EF 核心慣例。來看看將 DbContext 類別時,將不會有這個屬性的特殊對應。我的名稱為這兩個屬性不一致的原因是,才可看到和傳統 nam ing 比較傳統的對應中的差異。

接下來是 PersonFullName 類型 (從重新命名 Directive),顯示圖 4,這是值物件。我在前一個發行項中說明的 EF 核心 2.0 現在可讓您將它對應為擁有實體的任何實體擁有它,例如日本武士類別保存的值物件。PersonFullName 值物件,使用其他的型別和實體中的屬性。值物件有沒有自己的識別、 不可變且不是實體。前一個發行項,除了我有的說明也包含深入 Pluralsight 以及其他的文件中的值物件課程,Domain-Driven 設計基礎,建立 Steve Smith (bit.ly/PS-DDD)。有其他重要的 facet 值的物件,使用由 Jimmy Bogard ValueObject 基底類別 (bit.ly/13SWd9h) 來實作它們。

圖 4 PersonFullName 值物件

public class PersonFullName : ValueObject<PersonFullName> {
  public static PersonFullName Create (string first, string last) {
    return new PersonFullName (first, last);
  }
  public static PersonFullName Empty () {
    return new PersonFullName (null, null);
  }
  private PersonFullName () { }

  public bool IsEmpty () {
    if (string.IsNullOrEmpty (First) && string.IsNullOrEmpty (Last)) {
      return true;
    } else {
      return false;
    }
  }
  private PersonFullName (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;
}
}

PersonFullName 用來封裝使用實體或類型中的人員名稱的網域中的一般規則。有一些值得注意的功能,此類別。Alt hough 它尚未從較早版本變更,我並未提供在前一個發行項的完整清單。因此,有幾件事,尤其是空的處理站和 IsEmpty 方法這裡,解釋。在 EF 核心實作擁有實體的方式,因為它不可為 null 主控類別中項目。在我的網域,PersonFullName 用來儲存日本武士秘密的身分識別,但它必須先填入沒有規則。這會建立商務規則和 EF 核心規則之間的衝突。同樣地,我必須夠簡單的解決方案,我不要覺得需要建立和維護個別的資料模型,它不會影響日本武士的使用方式。我不想使用我的網域 API 去記 EF 核心規則,所以我建置了兩個的 factory 方法的任何人:如果您有和空白的值如果不這麼做,您可以使用建立。和 IsEmpty 方法可以快速決定 PersonFullName 的狀態。PersonFullName 做 prop erty 實體必須利用此邏輯,然後使用這些實體的任何人都不需要知道 EF 核心規則。

結合所有彙總根

最後,日本武士類別會列在圖 5。日本武士是彙總的根。彙總的根是守護者整個彙總,確保其內部物件的有效性,並讓它們保持一致。為此 aggre 閘道的根,日本武士類型是負責建立其進入、 引號和 SecretIdentity 屬性的方式和攔截舊。

圖 5 日本武士實體,也就是彙總根

public class Samurai {   
  public Samurai (string name): this() {
    Name = name;
    GuidId=Guid.NewGuid();
    IsDirty=true;
  }
  private Samurai () {
    _quotes = new List<Quote> ();
    SecretIdentity = PersonFullName.Empty ();
  }
  public int Id { get; private set; }
  public Guid GuidId{get;private set;}
  public string Name { get; private set; }
  public bool IsDirty { get; private set; }
 
  private readonly List<Quote> _quotes = new List<Quote> ();
  public IEnumerable<Quote> Quotes => _quotes.ToList ();
  public void AddQuote (string quoteText) {
     // TODO: Ensure this isn't a duplicate of an item already in Quotes collection
    _quotes.Add (Quote.Create(GuidId,quoteText));
     IsDirty=true;
  }

  private Entrance _entrance;
  private Entrance Entrance { get { return _entrance; } }
  public void CreateEntrance (int minute, string sceneName, string description) {
    _entrance = Entrance.Create (GuidId, minute, sceneName, description);
     IsDirty=true;
  }
  public string EntranceScene => _entrance?.SceneName;

  private PersonFullName SecretIdentity { get; set; }
  public string RevealSecretIdentity () {
    if (SecretIdentity.IsEmpty ()) {
      return "It's a secret";
    } else {
      return SecretIdentity.FullName ();
    }
  }
  public void Identify (string first, string last) {
    SecretIdentity = PersonFullName.Create (first, last);
    IsDirty=true;
  }
}

其他類別一樣,日本武士會有多載建構函式,也就是具現化新日本武士的唯一方式。當建立新的日本武士為日本武士的已知的名稱必須是唯一的資料。建構函式設定 Name 屬性,並也會產生 GuidId 屬性的值。將取得 SamuraiId 屬性填入資料庫。我的網域,不會相依於資料層,GuidId 屬性可確保有唯一的識別,且什麼用來連接到日本武士的非根實體 (進入和引號),即使日本武士尚未尚未保存及接受中的值Sam uraiId] 欄位中。建構函式會將附加": this()"呼叫建構函式鏈結中的無參數建構函式。[Parame] terless 建構函式 (提示: 它也會使用 EF 核心從查詢結果中建立物件時) 可確保引號集合會具現化,且會建立 SecretIdentity。這是使用空的 factory 方法,該位置。即使有人永遠不會使用日本武士撰寫程式碼會提供 SecretIdentity 屬性的值,EF 核心會滿足,因為屬性不是 null。

日本武士中引號的完整封裝 (encapsulation) 不是新的。我利用討論在先前的資料行 EF 核心 1.1 的 IEnumerable 的支援 (msdn.com/magazine/mt745093)。

完整封裝的進入屬性已變更從先前的範例中,只有兩個次要的方式。首先,會造成我從入口中移除的 factory 方法,我現在具現化它直接。第二,進入建構函式現在採用額外的值讓我傳遞中即使此時日本武士類別不目前正在執行包含下列額外的值的任何動作。

因為較早的範例有 SecretIdentity 屬性一些增強功能。首先的屬性原本是公用的與公用 getter 和 setter 私用。這允許 EF 核心與將它保存在舊版的 EF 的相同方式。現在,不過,SecretIdentity 宣告為私用屬性還定義了不支援屬性。保存的時間,EF 核心是能夠推斷支援屬性,讓它可以儲存及擷取此資料,沒有任何額外的對應,在 [我的組件。識別方法,其中您可以指定密碼的身分識別的第一個和最後一個名稱,就是在較早的範例。但在此情況下,如果您想要讀取的值,您可以透過存取它的公用屬性。現在,它隱藏的我已將加入新的方法,RevealSecretIdentity,將會判斷如果 prop erty 會擴展或不使用 PersonFullName.IsEmpty 方法。如果是的話,它會傳回 SecretIdentity FullName。但是,如果未識別的人員,則為 true 的身分識別,則方法會傳回字串:"這是密碼 」。

日本武士,bool,稱為 IsDirty 沒有新的屬性。修改日本武士屬性中,任何時候我設定 IsDirty 都為 true。我將使用其他位置的值,決定是否需要呼叫 SaveChanges 日本武士上。

因此在此彙總,沒有任何方法來避開建的實體與根規則日本武士。若要建立,唯一的方式修改或讀取進入、 引號,且 SecretIdentity 透過限制日本武士,其中保護整個彙總的彙總的根目錄中,為內建的邏輯。

對應至資料存放區使用 EF 核心 2.0

前一篇文章的焦點是在 EF 核心 2.0 的方式能夠持續儲存並擷取資料對應到這些 con 限制的類別。使用這個增強的網域模型,EF 核心會仍然可以使用對應的大部份甚至因此緊密封裝日本武士類別中的項目。在少數情況下不必提供一些說明] 5d; 並確定 DbContext 它 comprehends 如何將這些類別對應至資料庫中所示圖 6

圖 6 SamuraiContext DbContext 類別

public class SamuraiContext : DbContext {
  public DbSet<Samurai> Samurais { get; set; }
  protected override void OnConfiguring (DbContextOptionsBuilder optionsBuilder) {
    optionsBuilder.UseSqlite ("Filename=DP0917Samurai.db");
  }
  protected override void OnModelCreating (ModelBuilder modelBuilder) {
    modelBuilder.Entity<Samurai> ()
      .HasOne (typeof (Entrance), "Entrance")
      .WithOne ().HasForeignKey(typeof (Entrance), "SamuraiFk");

    foreach (var entityType in modelBuilder.Model.GetEntityTypes ()) {
      modelBuilder.Entity (entityType.Name).Property<DateTime> 
        ("LastModified");
      modelBuilder.Entity (entityType.Name).Ignore ("IsDirty");
    }
    modelBuilder.Entity<Samurai> ().OwnsOne (typeof (PersonFullName), 
      "SecretIdentity");
  }

  public override int SaveChanges () {
    foreach (var entry in ChangeTracker.Entries ()
      .Where (e => e.State == EntityState.Added ||
        e.State == EntityState.Modified)) {
      if (!(entry.Entity is PersonFullName))
        entry.Property ("LastModified").CurrentValue = DateTime.Now;
    }
    return base.SaveChanges ();
  }
}

未大量已變更 SamuraiContext 中自第一個樣本後從我的第一篇文章,,但有幾點備忘提醒做幾件事。例如,OwnsOne 對應可讓知道 SecretIdentity 是擁有實體和其屬性必須永續性的 EF 核心 * 好像日本武士的個別屬性。此範例中,為了我硬式編碼 OnConfiguring 方法,而不是利用相依性插入和逆轉控制 (IoC) 服務中的提供者。第一篇文章所述,EF 核心可以找出進入、 日本武士之間的一對一關聯性,但有表達關聯性,才能存取 HasForeignKey 方法,以通知非傳統的外部索引鍵內容屬性,SamuraiFk。這樣做,因為進入是私用在日本武士,我不能使用 lambda 運算式,HasForeignKey 參數使用替代語法

LastModifed 是陰影屬性 — EF 核心的新手,,即使它不是實體中的屬性會保存到資料庫。略過對應是確保在日本武士 IsDirty 屬性不會保存,因為它是僅針對網域相關的邏輯。

這樣就行了。指定的數量已套用在我的網域類別的 DDD 模式,沒有在我必須新增至通知 EF 核心 2.0 SamuraiContext 類別資料庫看起來像或如何儲存和擷取資料,從該資料庫的特殊對應進行極少。和我相當對的印象深刻的。

沒有這類的完美 DDD 範例

這是仍簡單的範例,所以以外輸出 「 密碼 」 時 SecretIdentity 尚未指定值,我不解決邏輯中任何複雜的問題。Eric Evan DDD 活頁簿的子標題是軟體的"解決複雜度核心。 」 非常多的 DDD 指引是有關細分過度複雜問題中較小的大小可解決的問題。程式碼設計模式是該片段。每個人都有不同的問題,若要解決其網域中,讀取器通常要求可以做為範本使用自己的軟體的範例。但所有的人分享我們的程式碼與想法可以做為提供 ex-amples 做為學習工具。您可以推斷這些經驗並套用部分的思考和決策的制訂您自己的問題。我可以更多的時間花在這個微小的位元的程式碼,從 DDD 利器,套用其他邏輯和模式,但此範例不很遠中運用 DDD 意見,以建立更深入的焦點,在行為上,而不是屬性,並進一步封裝和保護在彙總。

我這些兩個資料行中的目標是要顯示 EF 核心 2.0 許多更容易使用,將 DDD 已取得焦點的網域模型對應至您的資料庫。我所示範,希望您所也自治組織所導致的 DDD 模式時我已經在 cluded 這些類別中。


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

非常感謝下列 Microsoft 技術專家檢閱這篇文章:Cesar de La Torre


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