Łączny root z Entity Framework przy użyciu Domain Driven Design

Buduję aplikację przy użyciu Domain Driven Design, który używa Entity Framework.

Moim celem jest zezwolenie moim modelom domenowym (które utrwalają się w EF) zawierają w sobie pewną logikę.

Po wyjęciu z pudełka struktura encji jest dość nieograniczająca, jeśli chodzi o sposób dodawania jednostek do wykresu, a następnie utrwalania.

Weźmy na przykład moją domenę jako POCO (bez logiki):

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

Moja przykładowa domena jest taka, że ​​organizacja może mieć wielu ludzi. Osoba może należeć tylko do jednej organizacji.

Tworzenie organizacji i dodawanie do niej osób jest bardzo proste:

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

Moje zapytanie testowe to.

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

Powyższy układ klas nie jest zgodny z działaniem domeny. Na przykład wszyscy ludzie należą do organizacji z organizacją będącą łącznym korzeniem. Nie ma sensu robić tegocontext.People.Add(...) ponieważ nie tak działa domena.

Gdybyśmy chcieli dodać trochę logiki doOrganization model ograniczający liczbę osób w tej organizacji, możemy wdrożyć metodę.

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

Jednak przy obecnym układzie klas mogę ominąćAddPerson logika przez dowolne wywołanieorganization.Persons.Add(...) lub całkowicie zignoruj ​​zagregowany root, wykonująccontext.Persons.Add(...), z których żaden nie chcę zrobić.

Moje proponowane rozwiązanie (które nie działa i dlatego je tutaj zamieszczam) to:

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

To nie działa jako kod odwzorowaniaHasMany(o => o.People).WithRequired(p => p.Organization); nie kompiluje jakoHasMany oczekujeICollection<TEntity> i nieIReadOnlyCollection. Mogę odsłonićICollection sam, ale chcę uniknąć tegoAdd / Remove metody.

Mogę „Zignorować”People właściwość, ale nadal chcę być w stanie pisać zapytania Linq przeciwko niemu.

Moim drugim problemem jest to, że nie chcę, aby mój kontekst ujawniał możliwość dodawania / usuwania osób bezpośrednio.

W kontekście chciałbym:

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

Jednak EF nie zapełniPeople właściwość mojego kontekstuIDbSet narzędziaIQueryable. Jedyne rozwiązanie, które mogę wymyślić, aby napisać fasadęMyDbContext co ujawnia funkcjonalność, której chcę. Wydaje się przesadą i dużą konserwacją zestawu danych tylko do odczytu.

Jak uzyskać czysty model DDD podczas korzystania z Entity Framework?

EDYTOWAĆ
Używam Entity-Framework v5

questionAnswers(3)

yourAnswerToTheQuestion