Aggregate Root with Entity Framework Using Driven Driven Design

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

My goal is to let my domain models (which are saved with EF) contain some logic.

Out of the box, the entity-structure does not quite limit how objects are added to the chart and then saved.

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 sample domain is that an Organization can have many people. A person may belong to only one Organization.

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

 using (var context = new MyDbContext()) { var organization = new Organization { CompanyName = "Matthew 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 request.

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

The above class layout does not match how the domain works. For example, all people belong to an Organization with an organization that is an aggregate root. It makes no sense to do context.People.Add(...) as not how the domain works.

If we want to add some logic to the Organization model to limit the number of people in this 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 class layout, I can bypass the AddPerson logic either by calling organization.Persons.Add(...) or completely ignore the aggregate root by doing context.Persons.Add(...) , none of which I want to do .

My suggested solution (which does not work and why I post it here):

 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 because HasMany(o => o.People).WithRequired(p => p.Organization); conversion HasMany(o => o.People).WithRequired(p => p.Organization); does not compile because HasMany expects an HasMany ICollection<TEntity> , not an IReadOnlyCollection . I can show ICollection itself, but I want to avoid the 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 show the ability to directly add / remove people.

In the context I would like:

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

However, EF will not populate the People property of my context, even if IDbSet implements IQueryable . The only solution I can come up with is to write a facade over MyDbContext that provides the functionality I want. There seems to be an abundance and a lot of maintenance for a read-only dataset.

How to achieve a clean DDD model using Entity Framework?

EDIT
I am using Entity-Framework v5

+6
source share
3 answers

As you have noticed, the persistence framework (EF) imposes some requirements on the class structure, which makes it not "clean" as you expected. I am afraid that the struggle with him will end in endless struggle and brain strokes.

I would suggest a different approach, a completely clean domain model and a separate low-level persistence model. You will probably need a translation mechanism between the two, AutoMapper will do everything.

This will completely save you from your problems. There is no way to "take a slice" just because EF does the necessary things, and the context is not available at the domain level, since it is only from the "other world", it does not belong to the domain.

I saw people doing partial models (otherwise called "limited contexts") or just creating the usual EF poco structure and pretending to be DDD, but this is probably not the case and your problems hit the nail exactly in the head.

+14
source

Most of your problems come from free mapping, requiring entitiy properties to be publicly available, so you cannot properly encapsulate save data.

Consider using XML-based mapping (.edmx files) instead of free mapping. It allows you to display private properties.

One more note - your application should not directly use DbContext. Create an interface for it that provides only the DbSets of those objects that you have identified as aggregate roots.

+2
source

The Wiktor consultation is definitely worth the pondering. I persisted in the CORE data model and learned to live with some of the weaknesses of EF. I spent hours trying to get around them. Now I live with limitations and avoid the extra display layer. What was my priority.

However, if you do not see the display layer as a problem, use the DDD function with NO restrictions. that Wiktors offer is a way.

Some problems with EF:

  • Only supports a subset of types,
  • public get / set properties
  • Public get / set navigation system
  • lack of support for polymorphic type changes.
    • for example, Id Object in the database and Int in the subtype S1 and Guid in the subtype S2.
  • restrictions on how keys are built in a 1: 1 relationship ... And it's fast from head to head.

I had a script with a green field, and I needed to support only one layer, so I persisted. I would personally use DDD with restrictions again, even after experience. But to fully understand why someone can offer a display layer and a clean DDD model.

luck

+1
source

Source: https://habr.com/ru/post/950207/


All Articles