Entity Framework: объектный дизайн с отношениями и многое другое
У меня есть решение.net (с Entity Framework, использующим подход CodeFirst) об администрировании университета. Архитектура проекта выглядит следующим образом:
- DataAccess (DbContext и универсальный репозиторий)
- Сущности (код первых классов)
- Услуги (классы услуг)
- Веб (проект MVC, не важен для объяснения)
Для упрощения вопроса я расскажу только о двух классах из моего домена (на уровне сущностей):
- Курс
- Ученик
Курс может иметь много студентов. И целью решения является добавление студентов на курсы (с некоторой проверкой в середине).
Course.cs
public class Course
{
[Key]
public int IdCourse { get; protected set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; protected set; }
public void AddStudent(Student student)
{
// Some Validation
Students.Add(student);
}
}
Как видите, у класса есть свойство для добавления студентов в курс. Ситуация такова, что валидация растет и сейчас очень сложна, и ей нужно сделать несколько запросов к базе данных и многое другое... Так что у курса много обязанностей!
Поэтому я подумал, что мне нужен сервис для обработки "Добавление" и выполнения всей проверки и удаления этой ответственности из класса курса. Проблема в том, что, поскольку коллекция студентов является частным участником курса, я не могу добавлять студентов в курсы класса обслуживания.
Еще один способ, который я решил решить эту проблему, - это добавить связанную сущность (студента) в контекст (из службы), но это сломало бы мой репозиторий (потому что мне пришлось бы сделать DbContext общедоступным). И если я использую DbContext из класса Service напрямую, репозиторий не имеет смысла, не так ли?
Так, как я должен проектировать это? или что я должен сделать, чтобы решить проблему? Любое предложение тоже будет очень хорошо принято!
Спасибо!
2 ответа
Это не дает прямого ответа на ваш вопрос, особенно в отношении кода EF и всего этого (не в моей области знаний). Но похоже, что у вас есть проблема дизайна здесь.
Я большой поклонник DDD или доменно-управляемого дизайна. Некоторые вопросы, которые вы можете задать, включают в себя:
- Должен ли "AddStudents" действительно быть методом? У объекта должно быть поведение, в то время как "добавление учеников" является концепцией сохранения данных, а не концепцией домена / сущности. Возможно, поведение будет "зачислением"? Регистрация может иметь свою собственную логику и, следовательно, объекты для инкапсуляции этой логики.
- Во-вторых, существуют ли студенты вне концепции курса, который они посещают? Например, они могут пройти несколько курсов? Тогда, возможно, коллекция не должна быть приватной... должен ли быть новый объект с именем StudentEnrollment?
Есть и другие, но определенно взгляните на вашу проблему и то, как спроектированы ваши объекты. Является ли добавление студента курсом таким же, как добавление курса к этому студенту?
Возможно, ваша служба могла бы выполнить валидацию и либо добавить студента в курс, либо курс в студента (или оба) и сохранить их в своих собственных репозиториях.
В любом случае, я бы порекомендовал серию Джули Лерман по DDD для ориентированных на данные людей EF: http://msdn.microsoft.com/en-us/magazine/dn342868.aspx.
В любом случае, я бы порекомендовал не делать ваш DbContext публичным!
Я не согласен с таким методом, как AddStudent(...)
в сущности. Пусть сущности будут простыми объектами передачи данных - то есть POCO - в EntityFramework и из него. Я предпочитаю, чтобы моя бизнес-логика заботилась о таких вещах.
Вы можете установить IdCourse
а также Students
сеттер в приват. Я видел, как другие используют protected
как хорошо... но почему? Есть ли потомки сущности?
Одно решение для ICollection<T>
свойства, рекомендуемые подобными Джули Лерман, являются:
private ICollection<Student> _students;
public virtual ICollection<Student> Students {
get; { _students ?? (_students = new Collection<Student>()); }
private set { _students = value; }
}
Обновление (из комментария)
Если вы используете репозиторий или бизнес-уровень для запроса курса, у вас может быть следующий метод:
// this is how I would approach a business layer method
void AddStudentToCourse(string CourseName, Student NewStudent) {
// may employ a using block for repository
// also validate student
using (var repo = new CourseRepository()) {
var course = repo.GetCourse(CourseName);
course.Students.Add(NewStudent);
repo.Save();
}
}
Есть несколько различных способов сделать это, и этот пример далеко не полный, но он должен дать вам базовую концепцию.
Основной DbContext
(наследуется или инкапсулируется CourseRepository
) будет отслеживать изменения в Course
и добавление нового студента или отношения Student
к Course
,
Что касается использования частных или защищенных сеттеров, я хотел ограничить доступ разработчика к изменению идентификаторов / ключей или переназначению ссылок на всю коллекцию.