使用域驱动设计与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模型。
祝好运