Agregue la raíz con Entity Framework usando Domain Driven Design

Estoy creando una aplicación utilizando Domain Driven Design que utiliza Entity Framework.

Mi objetivo es permitir que los modelos de mi dominio (que persisten con EF) contengan cierta lógica dentro de ellos.

Fuera de la caja, entidad-marco es bastante restrictivo en cuanto a cómo las entidades se agregan al gráfico y luego persisten.

Tomemos, por ejemplo, mi dominio como POCO (sin 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; } 
}

Mi dominio de ejemplo es que una organización puede tener muchas personas. Una persona solo puede pertenecer a una organización.

Es muy sencillo crear una organización y agregarle personas:

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

Mi consulta de prueba es.

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

El diseño de clases anterior no se ajusta a cómo funciona el dominio. Por ejemplo, todas las personas pertenecen a una Organización con Organización como raíz agregada. No tiene sentido ser capaz de hacercontext.People.Add(...) ya que no es así como funciona el dominio.

Si quisiéramos añadir alguna lógica a laOrganization modelo para restringir la cantidad de personas que pueden estar en esa organización, podríamos implementar un 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;
}

Sin embargo, con el diseño actual de las clases puedo sortear elAddPerson lógica ya sea llamandoorganization.Persons.Add(...) o ignorar completamente la raíz agregada haciendocontext.Persons.Add(...), ninguno de los cuales quiero hacer.

Mi solución propuesta (que no funciona y es por eso que la publico aquí) es:

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

Esto no funciona como el código de mapeoHasMany(o => o.People).WithRequired(p => p.Organization); no compila comoHasMany espera unICollection<TEntity> y noIReadOnlyCollection. Puedo exponer unICollection Sí, pero quiero evitar tenerAdd / Remove metodos

Puedo "ignorar" laPeople propiedad, pero todavía quiero poder escribir consultas Linq contra ella.

Mi segundo problema es que no quiero que mi contexto exponga la posibilidad de agregar / eliminar personas directamente.

En el contexto me gustaría:

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

Sin embargo, EF no llenará elPeople propiedad de mi contexto, aunqueIDbSet implementosIQueryable. La única solución que se me ocurre es escribir una fachada sobreMyDbContext lo que expone la funcionalidad que quiero. Parece excesivo y mucho mantenimiento para un conjunto de datos de solo lectura.

¿Cómo puedo lograr un modelo DDD limpio al utilizar Entity Framework?

EDITAR
Estoy usando Entity-Framework v5

Respuestas a la pregunta(3)

Su respuesta a la pregunta