使用域驱动设计与实体框架聚合根

Mat*_*hew 7 c# domain-driven-design entity-framework aggregateroot

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

我的目标是允许我的域模型(通过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; } 
}
Run Code Online (Sandbox Code Playgroud)

我的示例域名是组织可以有很多人.一个人只能属于一个组织.

创建组织并向其添加人员非常简单:

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();
}
Run Code Online (Sandbox Code Playgroud)

我的测试查询是.

var organizationsWithSteve = context.Organizations.Where(o => o.People.Any(p => p.FirstName == "Steve"));
Run Code Online (Sandbox Code Playgroud)

上面的类布局不符合域的工作方式.例如,所有人都属于组织为组合根的组织.能够这样做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;
}
Run Code Online (Sandbox Code Playgroud)

但是,对于类的当前布局,我可以AddPerson通过调用organization.Persons.Add(...)或完全忽略聚合根来避开逻辑,这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(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

这不起作用的映射代码HasMany(o => o.People).WithRequired(p => p.Organization);不编译为HasMany预期的ICollection<TEntity>,而不是IReadOnlyCollection.我可以暴露ICollection自己,但我想避免使用Add/ Remove方法.

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

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

在上下文中我想要:

public IQueryable<Person> People { get; set; }
Run Code Online (Sandbox Code Playgroud)

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

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

编辑
我正在使用Entity-Framework v5

Wik*_*hla 14

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

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

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

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

  • 感谢您的建议,我最终切换到 NHibernate,因为它为我的类设计提供了更大的灵活性。 (2认同)
  • @Timo:没那么糟糕。如果您依赖显式映射 (`CreateMap&lt;T,U&gt;`) 并尽早创建映射(在 **Composition Root** 中)并调用 `Mapper.AssertConfigurationIsValid`,则 AutoMapper 会在您创建映射后立即报告模型之间的任何不兼容性。启动你的应用程序。通过这种技术,我们几年来都遇到了映射不兼容性的**零**问题(因为这些问题很快就被消除了;事实上,您甚至无法运行应用程序,因为 `AssertConfigurationIsValid` 只是抛出异常)。然而,你的言论当然是正确且重要的。谢谢。 (2认同)