使用域驱动设计的实体框架聚合根 [英] Aggregate root with Entity Framework using Domain Driven Design

查看:144
本文介绍了使用域驱动设计的实体框架聚合根的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我的目标是允许我的域模型(持续使用EF)包含一些



开箱即用,实体框架对于实体如何添加到图形中,然后持续存在是非常非限制性的。

$ b $以b

为例,我的域名为POCO(无逻辑):

  public class Organization 
{
private ICollection< Person> _people = new List< Person>();

public int ID {get;组; }

public string CompanyName {get;组; }

public virtual ICollection< Person>人{get {return _people; } protected set {_people = value; }}
}

public class Person
{
public int ID {get;组; }

public string FirstName {get;组; }
public string LastName {get;组; }

public virtual组织机构{get;保护套}
}

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 = )\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>组织{get;组; }
public IDbSet< Person>人{get;组; }
}

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



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

  using(var context = new MyDbContext())
{
var organization = new Organization
{
CompanyName =Matthew's Widget工厂
};

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 模型添加一些逻辑,以限制该组织中有多少人,那么我们可以实现一种方法。 p>

  public Person AddPerson(string firstName,string lastName)
{
if(People.Count()> = 5)
{
throw new InvalidOperationException(您的组织已经达到最大容量);
}

var person = new Person(firstName,lastName);
this.People.Add(person);
退货人;
}

然而,使用当前的类布局,我可以规避 AddPerson 通过调用 organization.Persons.Add(...)或完全忽略聚合根,执行 context.Persons.Add(...),我不想这样做。



我提出的解决方案工作,是为什么我在这里发布)是:

  public class Organization 
{
private列表与LT;人> _people = new List< Person>();

// ...

protected virtual List< Person> WritablePeople
{
get {return _people; }
set {_people = value; }
}

public virtual IReadOnlyCollection< Person>人{get {return People.AsReadOnly();

public void AddPerson(string firstName,string lastName)
{
//做域逻辑/验证

WriteablePeople.Add );
}
}

这不起作用的映射代码 HasMany(o => o.People).WithRequired(p => p.Organization); 不编译为 HasMany 期望一个 ICollection< TEntity> 而不是 IReadOnlyCollection 。我可以公开一个 ICollection 本身,但是我想避免使用添加 / 删除方法。



我可以忽略 People 属性,但我仍然想要能够编写Linq查询。



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



在上下文中,我想要:

  public IQueryable& ;人{get;组;但是,EF不会填入 People 属性,即使 IDbSet 实现 IQueryable 。唯一的解决方案,我可以想出这样做,在$ code> MyDbContext 中写出一个立面,暴露了我想要的功能。似乎是一个只读数据集的维护和维护。



在实体框架中如何实现干净的DDD模型?



编辑

我正在使用Entity-Framework v5

解决方案

正如你所注意到的那样,持久化基础设施(EF)对类结构施加了一些要求,从而使其不像您所期望的那样干净。我恐怕与之奋斗的最终将是无休止的斗争和脑力障碍。



我建议另一种方法,一个完全干净的领域模型和一个单独的持久性模型下层。你可能需要这两者之间的翻译机制,AutoMapper会做的很好。



这将彻底解决您的疑虑。因为EF使得事情变得必要,并且上下文不能从域层获得,因为它只是来自另一个世界,它不属于域,所以没有办法剪切。



我已经看到制作部分模型(又称有界上下文)的人,或者只是创建一个普通的EF poco结构,并假装这个IS DDD,但它可能不是,你的关注点击钉子正好在头上。


I am building an application using Domain Driven Design that is using Entity Framework.

My goal is to allow my domain models (that get persisted with EF) contain some logic within them.

Out of the box, entity-framework is pretty nonrestrictive as to how entities get added to the graph and then persisted.

Take for example, my domain as POCO (without logic):

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; } 
}

My Example domain is that an Organization can have many people. A person can only belong to one Organization.

This is very simple to create an organization and add people to it:

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();
}

My test query is.

var organizationsWithSteve = context.Organizations.Where(o => o.People.Any(p => p.FirstName == "Steve"));

The above layout of classes doesn't conform to how the domain works. For example, all people belong to an Organization with Organization being the aggregate root. It doesn't make sense to be able to do context.People.Add(...) as that's not how the domain works.

If we wanted to add some logic to the Organization model to restrict how many people can be in that organization, we could implement a method.

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;
}

However, with the current layout of classes I can circumvent the AddPerson logic by either calling organization.Persons.Add(...) or completely ignore the aggregate root by doing context.Persons.Add(...), neither of which I want to do.

My proposed solution (which doesn't work and is why I'm posting it here) is:

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(...);
    }
}

This does not work as the mapping code HasMany(o => o.People).WithRequired(p => p.Organization); does not compile as HasMany expects an ICollection<TEntity> and not IReadOnlyCollection. I can expose an ICollection itself, but I want to avoid having Add / Remove methods.

I can "Ignore" the People property, but I still want to be able to write Linq queries against it.

My second problem is that I do not want my context to expose the possibility to Add / Remove people directly.

In the context I would want:

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

However, EF will not populate the People property of my context, even though IDbSet implements IQueryable. The only solution I can come up with to this to write a facade over MyDbContext which exposes the functionality I want. Seems overkill and a lot of maintenance for a read-only dataset.

How do I achieve a clean DDD model while using Entity Framework?

EDIT
I'm using Entity-Framework v5

解决方案

As you noticed, the persistence infrastructure (the EF) imposes some requirements on the class structure thus making it not "as clean" as you'd expect. I am afraid that struggling with it would end up with endless struggle and brain bumps.

I'd suggest another approach, a completely clean domain model and a separate persistence model in a lower layer. You probably would need a translation mechanism between these two, the AutoMapper would do fine.

This would free you from your concerns completely. There are no ways to "take a cut" just because the EF makes things necessary and the context is not available from the domain layer as it is just from "another world", it doesn't belong to the domain.

I've seen people making partial models (aka "bounded contexts") or just creating an ordinary EF poco structure and pretending this IS DDD but it probably isn't and your concerns hit the nail precisely in the head.

这篇关于使用域驱动设计的实体框架聚合根的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆