Agregar raiz com o Entity Framework usando o Design Dirigido por Domínio

Eu estou construindo um aplicativo usando o Domain Driven Design que está usando o Entity Framework.

Meu objetivo é permitir que meus modelos de domínio (que persistem com EF) contenham alguma lógica dentro deles.

Fora da caixa, o framework de entidades é bastante não restritivo quanto ao modo como as entidades são adicionadas ao gráfico e depois persistidas.

Tomemos por exemplo, meu domínio como POCO (sem lógica):

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

Meu domínio de exemplo é que uma organização pode ter muitas pessoas. Uma pessoa só pode pertencer a uma organização.

Isso é muito simples para criar uma organização e adicionar pessoas a ela:

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

Minha consulta de teste é.

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

O layout de classes acima não está de acordo com o funcionamento do domínio. Por exemplo, todas as pessoas pertencem a uma organização com organização sendo a raiz agregada. Não faz sentido ser capaz de fazercontext.People.Add(...) como não é assim que o domínio funciona.

Se quiséssemos adicionar alguma lógica aoOrganization modelo para restringir quantas pessoas podem estar nessa organização, poderíamos implementar um método.

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

No entanto, com o layout atual das classes, posso contornarAddPerson lógica por qualquer chamadaorganization.Persons.Add(...) ou ignorar completamente a raiz agregada, fazendocontext.Persons.Add(...), nenhum dos quais eu quero fazer.

Minha solução proposta (que não funciona e é por isso que estou postando aqui) é:

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

Isso não funciona como o código de mapeamentoHasMany(o => o.People).WithRequired(p => p.Organization); não compila comoHasMany espera umICollection<TEntity> e nãoIReadOnlyCollection. Posso expor umICollection em si, mas eu quero evitar terAdd / Remove métodos.

Eu posso "ignorar" oPeople propriedade, mas eu ainda quero ser capaz de escrever consultas Linq contra ele.

Meu segundo problema é que não quero que meu contexto exponha a possibilidade de adicionar / remover pessoas diretamente.

No contexto, eu gostaria de:

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

No entanto, a EF não preencherá oPeople propriedade do meu contexto, emboraIDbSet implementaIQueryable. A única solução que posso fazer é escrever uma fachada sobreMyDbContext que expõe a funcionalidade que eu quero. Parece um exagero e muita manutenção para um conjunto de dados somente leitura.

Como faço para obter um modelo DDD limpo ao usar o Entity Framework?

EDITAR
Estou usando o Entity-Framework v5

questionAnswers(3)

yourAnswerToTheQuestion