2013-07-24 73 views
6

我正在使用使用實體框架的域驅動設計構建應用程序。使用域驅動設計與實體框架聚合根

我的目標是讓我的領域模型(即持續使用EF)在其中包含一些邏輯。

開箱即用,entity-framework對於實體如何添加到圖中然後保持不變是非常有限的。

舉個例子,我作爲POCO網域(不含邏輯):

public class Organization 
{ 
    private ICollection<Person> _people = new List<Person>(); 

    public int ID { get; set; } 

    public string CompanyName { get; set; } 

    public virtual ICollection<Person> People { get { return _people; } protected set { _people = value; } } 
} 

public class Person 
{ 
    public int ID { get; set; } 

    public string FirstName { get; set; } 
    public string LastName { get; set; } 

    public virtual Organization Organization { get; protected set; } 
} 

public class OrganizationConfiguration : EntityTypeConfiguration<Organization> 
{ 
    public OrganizationConfiguration() 
    { 
     HasMany(o => o.People).WithRequired(p => p.Organization); //.Map(m => m.MapKey("OrganizationID")); 
    } 
} 

public class PersonConfiguration : EntityTypeConfiguration<Person> 
{ 
    public PersonConfiguration() 
    { 
     HasRequired(p => p.Organization).WithMany(o => o.People); //.Map(m => m.MapKey("OrganizationID")); 
    } 
} 

public class MyDbContext : DbContext 
{ 
    public MyDbContext() 
     : base(@"Data Source=(localdb)\v11.0;Initial Catalog=stackoverflow;Integrated Security=true") 
    { 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Configurations.Add(new PersonConfiguration()); 
     modelBuilder.Configurations.Add(new OrganizationConfiguration()); 
    } 

    public IDbSet<Organization> Organizations { get; set; } 
    public IDbSet<Person> People { get; set; } 
} 

我的例子中域是一個組織可以有很多的人。一個人只能屬於一個組織。

這是非常簡單的創建一個組織,並添加人是:

using (var context = new MyDbContext()) 
{ 
    var organization = new Organization 
    { 
     CompanyName = "Matthew's Widget Factory" 
    }; 

    organization.People.Add(new Person {FirstName = "Steve", LastName = "McQueen"}); 
    organization.People.Add(new Person {FirstName = "Bob", LastName = "Marley"}); 
    organization.People.Add(new Person {FirstName = "Bob", LastName = "Dylan" }); 
    organization.People.Add(new Person {FirstName = "Jennifer", LastName = "Lawrence" }); 

    context.Organizations.Add(organization); 

    context.SaveChanges(); 
} 

我的測試查詢。

var organizationsWithSteve = context.Organizations.Where(o => o.People.Any(p => p.FirstName == "Steve")); 

上述類的佈局不符合域的工作方式。例如,所有人員都屬於一個組織是組織的集合根。因爲這不是域的工作方式,所以能夠做到context.People.Add(...)沒有意義。

如果我們想爲Organization模型添加一些邏輯來限制該組織中有多少人,我們可以實現一種方法。

public Person AddPerson(string firstName, string lastName) 
{ 
    if (People.Count() >= 5) 
    { 
     throw new InvalidOperationException("Your organization already at max capacity"); 
    } 

    var person = new Person(firstName, lastName); 
    this.People.Add(person); 
    return person; 
} 

然而,隨着類的當前佈局我可以通過調用organization.Persons.Add(...)規避AddPerson邏輯或通過執行context.Persons.Add(...),兩者都不我想要做完全忽略該聚合根。

我所提出的解決方案(這就是爲什麼我在這裏張貼不工作,爲)是:

public class Organization 
{ 
    private List<Person> _people = new List<Person>(); 

    // ... 

    protected virtual List<Person> WritablePeople 
    { 
     get { return _people; } 
     set { _people = value; } 
    } 

    public virtual IReadOnlyCollection<Person> People { get { return People.AsReadOnly(); } } 

    public void AddPerson(string firstName, string lastName) 
    { 
        // do domain logic/validation 

     WriteablePeople.Add(...); 
    } 
} 

這不作爲映射代碼HasMany(o => o.People).WithRequired(p => p.Organization);工作不編譯爲HasMany期望一個ICollection<TEntity>和不是IReadOnlyCollection。我可以公開一個ICollection本身,但我想避免使用Add/Remove方法。

我可以「忽略」People屬性,但我仍然希望能夠針對它編寫Linq查詢。

我的第二個問題是我不希望我的上下文暴露直接添加/刪除人員的可能性。

在上下文我會想:

public IQueryable<Person> People { get; set; } 

然而,EF不會填充我的上下文的People財產,即使IDbSet實現IQueryable。我可以想出的唯一解決方案是通過MyDbContext寫出一個正面,它揭示了我想要的功能。似乎矯枉過正和大量的只讀數據集的維護。

如何在使用實體框架時實現乾淨的DDD模型?

編輯
我使用實體框架V5

+0

您需要將其標記爲虛擬以便自動填充。 – user1496062

回答

14

正如你注意到的,持久性的基礎設施(生態足跡)對該類結構的一些要求,從而使之不「乾淨」爲你」 d期望。我擔心與它鬥爭最終會導致無盡的掙扎和腦筋急轉彎。

我建議另一種方法,一個完全乾淨的域模型和一個單獨的持久模型在較低層。您可能需要這兩者之間的翻譯機制,AutoMapper會很好。

這將完全釋放你的擔憂。僅僅因爲EF做出必要的事情並且上下文不能從域層獲得,因爲它僅僅來自「另一個世界」,沒有辦法「切入」,因爲它不屬於域。

我見過有人制作部分模型(又名「有界上下文」),或者只是創建一個普通的EF poco結構並假裝這是ISDD,但它可能不是,而您的擔心可能正好在頭上。

+1

感謝您的建議,我最終轉向使用NHibernate,因爲它爲我的課程設計提供了更多的靈活性。 – Matthew

+0

我同意你的意見。 REPOSITORY模式是數據訪問層和域層之間的接口。 EF更像是一個Data Mapper而不是一個REPOSITORY,因爲EF模型往往只不過是簡單的數據容器對象(這真的強化了它更像是一個Data Mapper的概念)。您最好實現一個使用存儲庫轉換數據映射圖層中的對象的域圖層(數據映射對象由EF提供​​,其中的數據模型由EF數據模型表示)。此外,這將符合DDD。 – fourpastmidnight

1

Wiktor的建議當然值得長期考慮。我堅持使用CORE Data模型,並學會了解EF的一些弱點。我花了數小時試圖繞過它們。 我現在生活的限制,並避免了額外的映射層。這是我的首要任務。

但是,如果您沒有看到映射層作爲問題,請使用無限制的DDD模型。那麼Wiktors的建議就是這樣。

與EF的一些問題:

  • 只支持類型的子集,
  • 性質的公共get/set方法
  • 導航公共的get/set
  • 沒有多態型變異的支持。
    • 例如Id基於Object的對象和S1中的子類型中的Int和Sub子類型S2中的Guid。
  • 限制密鑰在1:1關係中的構建方式 ......這就是我的頭頂。

我有一個綠色的場景場景,只想要1層維護,所以我堅持。 即使在經驗之後,我也會親自使用帶有限制的DDD。 但完全理解爲什麼有人可能會建議映射層和純粹的DDD模型。

好運

2

你的大多數問題都來自於需要entitiy屬性流利的映射是公共的,所以你不能正確封裝持久性細節。

考慮使用基於XML的映射(.edmx文件)而不是流利的映射。它允許您映射私有屬性。

另一件需要注意的事項 - 你的應用程序不應該直接使用DbContext。爲它創建一個接口,僅公開那些被標識爲聚合根的實體的DbSets。

+1

您可以使用Code First將私人財產與一個額外的代碼進行映射,如下所示:http://romiller.com/2012/10/01/mapping-to-private-properties-with-code-first/ – Yorro

+0

感謝您的黑客攻擊。下次我會試一試。 :) –

+0

實際上,雖然你想私人領域不屬性.... –