我正在使用领域驱动设计构建一个应用程序,该应用程序使用实体框架。
我的目标是允许我的领域模型(使用EF持久化)包含一些逻辑。
默认情况下,实体框架对于如何将实体添加到图形中并进行持久化相当宽松。
以我的领域为例,它是一个没有逻辑的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(...)
或完全忽略聚合根而执行context.Persons.Add(...)
来规避AddPerson
逻辑,这两种方式我都不想采取。我的解决方案建议(它并不起作用,这也是我在此发帖的原因)是:
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; }
然而,即使IDbSet
实现了IQueryable
,EF也不会填充我的上下文中的People
属性。我能想到的唯一解决方案是编写一个门面模式覆盖MyDbContext
,它公开了我想要的功能。这似乎对于只读数据集来说过于繁琐且需要大量维护。
在使用Entity Framework的同时如何实现干净的DDD模型?
编辑
我正在使用Entity Framework v5