2013-07-24 74 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

实际上,虽然你想私人领域不属性.... –