10

我正在使用使用实体框架的域驱动设计构建应用程序。

我的目标是允许我的域模型(使用 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;
}

但是,使用当前的类布局,我可以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(...);
    }
}

这不起作用,因为映射代码HasMany(o => o.People).WithRequired(p => p.Organization);没有按HasMany预期编译 anICollection<TEntity>和 not IReadOnlyCollection。我可以公开一个ICollection本身,但我想避免拥有Add/Remove方法。

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

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

在我想要的上下文中:

public IQueryable<Person> People { get; set; }

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

如何在使用实体框架时实现干净的 DDD 模型?

编辑
我正在使用实体框架 v5

4

3 回答 3

14

正如您所注意到的,持久性基础架构(EF)对类结构提出了一些要求,因此它不像您期望的那样“干净”。恐怕与它的斗争最终会导致无休止的斗争和大脑颠簸。

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

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

我见过人们制作部分模型(又名“有界上下文”),或者只是创建一个普通的 EF poco 结构并假装这是 DDD,但它可能不是,你的担忧正好击中了头脑。

于 2013-07-24T20:17:29.870 回答
3

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

但是,如果您没有将映射层视为问题,请使用没有限制的 DDD 模型。那么Wiktors的建议就是这样。

EF的一些问题:

  • 仅支持类型的子集,
  • 属性公共获取/设置
  • 导航公共获取/设置
  • 不支持多态类型变化。
    • 例如,基类中的 Id 对象和子类型 S1 中的 Int 和子类型 S2 中的 Guid。
  • 对如何在 1:1 关系中构建键的限制......这很快就让我想到了。

我有一个绿地场景,只需要维护一层,所以我坚持了下来。即使在体验之后,我个人也会再次使用有限制的 DDD。但是完全理解为什么有人会建议一个映射层和纯 DDD 模型。

祝你好运

于 2013-07-25T01:44:25.277 回答
2

您的大多数问题来自要求实体属性公开的流畅映射,因此您无法正确封装持久性细节。

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

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

于 2013-07-25T11:16:45.463 回答