使用域驱动设计与entity framework聚合根

我正在使用使用Entity Framework的Domain Driven Design构建应用程序。

我的目标是允许我的域模型(通过EF持久化)包含一些逻辑。

开箱即用,entity framework对于如何将实体添加到图形然后保持不变非常不受限制。

举例来说,我的域名为POCO(没有逻辑):

public class Organization { private ICollection _people = new List(); public int ID { get; set; } public string CompanyName { get; set; } public virtual ICollection 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 { public OrganizationConfiguration() { HasMany(o => o.People).WithRequired(p => p.Organization); //.Map(m => m.MapKey("OrganizationID")); } } public class PersonConfiguration : EntityTypeConfiguration { 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 Organizations { get; set; } public IDbSet 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模型中添加一些逻辑来限制该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 _people = new List(); // ... protected virtual List WritablePeople { get { return _people; } set { _people = value; } } public virtual IReadOnlyCollection 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而不是IReadOnlyCollection 。 我可以公开ICollection本身,但我想避免使用Add / Remove方法。

我可以“忽略” People属性,但我仍然希望能够针对它编写Linq查询。

我的第二个问题是我不希望我的上下文暴露直接添加/删除人的可能性。

在我想要的上下文中:

 public IQueryable People { get; set; } 

但是,即使IDbSet实现了IQueryable ,EF也不会填充我的上下文的People属性。 我可以想到的唯一解决方案是在MyDbContext上编写一个外观,它暴露了我想要的function。 对于只读数据集来说,似乎过度杀伤和大量维护。

如何在使用Entity Framework时实现干净的DDD模型?

编辑
我正在使用Entity-Framework v5

正如您所注意到的,持久性基础结构(EF)对类结构施加了一些要求,因此不会像您期望的那样“干净”。 我担心与之斗争会最终导致无休止的斗争和脑力障碍。

我建议另一种方法,一个完全干净的域模型和一个单独的持久性模型在较低层。 您可能需要在这两者之间使用转换机制,AutoMapper会很好。

这将完全解除您的顾虑。 没有办法“削减”只是因为EF使事情变得必要并且域层不能提供上下文,因为它只是来自“另一个世界”,它不属于域。

我已经看到人们制作部分模型(又名“有界背景”)或只是创建一个普通的EF poco结构并假装这个IS DDD,但它可能不是,而你的担忧正好在头脑中。

您的大多数问题都来自流畅的映射,要求将属性公开为公共,因此您无法正确封装持久性详细信息。

考虑使用基于XML的映射(.edmx文件)而不是流畅的映射。 它允许您映射私有属性。

另外需要注意的是 – 您的应用程序不应该直接使用DbContext。 为其创建一个接口,该接口仅公开您标识为聚合根的那些实体的DbSet。

Wiktor的建议当然值得长期考虑。 我坚持使用CORE Data模型并学会了解EF的一些弱点。 我花了好几个小时试图绕过他们。 我现在忍受了限制,并避免了额外的映射层。 这是我的首要任务。

但是,如果您没有看到映射层作为问题,请使用没有限制的DDD模型。 然后Wiktors的建议就是这样。

EF的一些问题:

  • 仅支持类型的子集,
  • 属性public get / set
  • 导航公共获取/设置
  • 没有多态类型变异支持。
    • 例如,基础中的Id对象和子类型S1中的Int以及子类型S2中的Guid。
  • 关于如何在1:1关系中建立密钥的限制……而这很快就会脱离我的头顶。

我有一个绿色的场景,只想维持1层,所以我坚持了下来。 即使在经历之后,我个人也会再次使用带有限制的DDD。 但完全理解为什么有人会提出映射层和纯DDD模型。

祝好运