Łą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