В многоуровневой архитектуре, использующей Entity Framework, должен ли я возвращать классы POCO из BLL? (Требуется руководство по архитектуре)

#c# #entity-framework #architecture #mvvm #poco

#c# #entity-framework #архитектура #mvvm #poco

Вопрос:

Вероятно, я слишком много читал и страдаю от некоторой информационной перегрузки. Поэтому я был бы признателен за некоторые четкие указания.

Из того, что я собрал, я могу использовать шаблон T4 от VS2010 для генерации классов POCO, которые не привязаны напрямую к EF. Я бы разместил их в их собственном проекте, в то время как мой DAL имел бы класс, производный от ObjectContext, верно?

Как только у меня появятся эти классы, приемлемо ли использовать их на уровне пользовательского интерфейса? То есть, скажем, один из сгенерированных классов BookInfo содержит информацию о книгах для публичной библиотеки (название, издание, страницы, краткое содержание и т.д.).

Мой BLL будет содержать класс BooksBLL , например, такой:

 public class BooksBLL
{
    ObjectContext _context;

    public void AddBook(BookInfo book) { ... }
    public void DeleteBook(int bookID) { ... }
    public void UpdateBook(int bookID, BookInfo newBook) { ... }

    //Advanced search taking possibly all fields into consideration
    public List<BookInfo> ResolveSearch(Func<BookInfo, bool> filter) { ... }

    //etc...
}
  

Итак, мои ViewModels в моем приложении MVVM UI будут взаимодействовать с вышеупомянутым классом BLL и обмениваться экземплярами BookInfo. Это нормально?

Кроме того, сообщения MVVM в Интернете предлагают реализацию IDataErrorInfo в целях проверки. Ничего, если я реализую указанный интерфейс в сгенерированном классе POCO? Я вижу из примеров, что эти сгенерированные классы POCO содержат все виртуальные свойства и содержимое, и я надеюсь, что добавление моей собственной логики будет в порядке?

Если это имеет какое-либо значение, в настоящее время мое приложение не использует WCF (или любой сетевой материал).

Кроме того, если вы видите что-то ужасно неправильное в том, как я пытаюсь создать свой BLL, пожалуйста, не стесняйтесь предлагать помощь и в этой области.

Обновление (Дополнительная информация по запросу):

Я пытаюсь создать приложение для автоматизации библиотеки. В настоящее время он не основан на сети.

Я думаю о наличии следующих слоев:

  • Проект, состоящий из сгенерированных классов POCO (BookInfo, Author, Member, Publisher, Contact и т.д.)
  • Проект с классом, производным от ObjectContext (DAL?)
  • Уровень бизнес-логики с классами, подобными тому, который я упомянул выше (BooksBLL, AuthorsBLL и т.д.)
  • Уровень пользовательского интерфейса WPF, использующий шаблон MVVM. (Отсюда мой дополнительный вопрос о реализации IDataErrorInfo).

Итак, мне интересно о таких вещах, как использование экземпляра BooksBLL в классе ViewModel, вызов ResolveSearch() его для получения List<BookInfo> и представление его … то есть, использование классов POCO везде.

Или у меня должны быть дополнительные классы, которые отражают классы POCO, предоставленные из моего BLL?

Если требуется дополнительная информация, пожалуйста, спросите.

Комментарии:

1. Первое, что, думаю, вам следует знать, это то, что на этот вопрос нет «правильного» ответа — вместо этого есть много решений, которые работают по-другому. Нелегко выбрать тот, который идеально соответствует вашим потребностям. Вот почему вы получаете информационную перегрузку. Чтобы дать «правильный» ответ, нам понадобится немного больше информации о приложении, которое вы создаете.

2. @Mark: Я понимаю, насколько руководство по архитектуре было бы субъективным, но как насчет конкретных вопросов, т. Е. использования классов POCO на уровне пользовательского интерфейса и реализации IDataErrorInfo в классах POCO? В любом случае, я постараюсь добавить больше деталей о моем проекте.

3. @ForeverLearn: Если вы реализуете IDataErrorInfo, ваши классы больше не являются POCO. Вся идея POCOs заключается в том, что они не содержат кода, относящегося к внешним проблемам. Таким образом, если вы используете IDataErrorInfo для облегчения проверки формы, вы больше не имеете дело с POCOs. Насколько я понимаю, в MVVM «Модель» — это модель приложения, а не модель домена, поэтому POCOs вашего домена не будут привязаны к элементам управления вашего пользовательского интерфейса. Я не знаком с примерами, о которых вы говорите, но я, конечно, надеюсь, что можно добавить свою собственную логику в классы бизнес-логики, иначе они были бы довольно бессмысленными.

4. @CodeMonkey1: О … <_>. Значит ли это, что мне нужно создать совершенно новый набор почти эквивалентных классов в моем BLL? (BookInfoBLL, AuthorBLL и т.д.)?

5. Я не эксперт в этом вопросе, что бы там ни было, но я почти уверен, что с этим не должно быть проблем. Я думаю, что это также одна из причин EFCodeFirst (взгляните на это, если вы еще этого не сделали, потому что это должно вам сильно помочь)

Ответ №1:

То, что вы делаете, в основном является шаблоном репозитория, для которого Entity Framework и POCO отлично подходят.

Итак, мои ViewModels в моем приложении MVVM UI будут взаимодействовать с вышеупомянутым классом BLL и обмениваться экземплярами BookInfo. Это нормально?

Это именно то, для чего предназначены объекты POCO; нет разницы между классами, которые генерируются, и тем, как вы написали бы их вручную. Это ваш ObjectContext, который инкапсулирует всю логику, связанную с сохранением любых изменений в базе данных, и это напрямую не отображается в вашем пользовательском интерфейсе.

Я лично не знаком с IDataErrorInfo , но если прямо сейчас ваши объекты будут использоваться только в этом одном приложении, я не вижу причин не помещать их непосредственно в сгенерированные классы. Добавление его в шаблон T4 было бы идеальным, если это возможно, это избавило бы вас от необходимости кодировать его вручную для каждого класса, если сообщения об ошибках следуют какому-либо логическому шаблону.

Кроме того, если вы видите что-то ужасно неправильное в том, как я пытаюсь создать свой BLL, пожалуйста, не стесняйтесь предлагать помощь и в этой области.

Это ни в коем случае не ужасно неправильно, но если вы планируете писать модульные тесты для своего BLL (что я бы рекомендовал), вы захотите изменить свой ObjectContext элемент на IObjectContext . Таким образом, вы можете заменить любой класс, реализующий IObjectContext интерфейс во время выполнения (например, ваш фактический ObjectContext ), что позволит вам проводить тестирование в контексте в памяти (т. Е. издеваться) и не обращаться к базе данных.

Аналогичным образом, подумайте о замене вашего List<BookInfo> интерфейсом какого-либо типа, такого как IList<BookInfo> или IBindingList<BookInfo> или наименьший общий знаменатель IEnumerable<BookInfo> . Таким образом, вы не привязаны напрямую к определенному классу List<T> , и если ваши потребности со временем меняются, что имеет тенденцию происходить, это сократит рефакторинг, необходимый для замены вашего List<BookInfo> на что-то другое, предполагая, что то, чем вы его заменяете, реализует выбранный вами интерфейс.

Комментарии:

1. Спасибо за предложения. Я немного разберу их.

Ответ №2:

Вам не нужно делать что-то конкретное … как сказал Марк, «правильного» ответа не существует. Однако, если ваше приложение достаточно простое, чтобы вы просто дублировали свои классы (например, BookInfoUI amp; BookInfoBLL), тогда я бы рекомендовал просто использовать бизнес-классы. Дополнительный уровень не будет служить цели, и поэтому он не должен существовать. Эрик Эванс из DDD даже рекомендует поместить всю вашу логику на уровень пользовательского интерфейса, если ваше приложение простое и имеет очень мало бизнес-логики.

Чтобы провести различие, на прикладном уровне должны быть классы, которые моделируют то, что происходит внутри приложения, а на уровне домена должны быть классы, которые моделируют то, что происходит в домене. Например, если у вас есть страница поиска, уровень пользовательского интерфейса может извлекать список объектов BookSearchResult из BookSearchService на прикладном уровне, который будет использовать домен для извлечения списка BookInfo.

Комментарии:

1. Ваш пример на самом деле привел меня к этому вопросу в первом palace. Я думал, что мой класс ‘BookSearchResult’ на самом деле ничем не отличается от обычного класса BookInfo… итак, какой смысл в создании нового класса, и я подумал, могу ли я напрямую использовать класс BookInfo в моей ViewModel (как сгенерированный POCO).

2. Ну, я бы предположил, что BookInfo содержит свойства для Title, Author, Publisher, и что последние два из них являются сложными типами со своими собственными наборами свойств. Но действительно ли BookSearchResult заботится обо всех деталях, или, возможно, ему просто нужны BookTitle, AuthorName, PublisherName? Это тривиальный пример, но вы можете начать видеть, как BookSearchResult моделирует именно то, что нам нужно для результата поиска, без учета того, что может присутствовать или не присутствовать в объекте Book нашего бизнес-уровня.

3. Я думаю, вы могли бы быть правы. У меня отображаются ВСЕ материалы, связанные с BookInfo, но только имена авторов и название издателя. И, возможно, также AuthorID, чтобы включить щелчок по имени автора и открытие связанного представления, отображающего сведения об авторе. НО, скажем, для AddBook () мне все равно пришлось бы передавать зеркальное отображение исходного BookInfo … за исключением того, что в нем реализован IDataErrorInfo. Хм…

4. Если вы не предвидите последующих дополнительных сложностей, то, похоже, вы могли бы обойтись только уровнем пользовательского интерфейса и доступа к данным, и ваши классы сущностей могли бы реализовать любые интерфейсы, которые им нужны для привязки пользовательского интерфейса и проверки.

5. Хорошо. Спасибо за последующие соображения.

Ответ №3:

Ответы на ваши вопросы могут зависеть от размера и сложности вашего приложения. Поэтому я боюсь, что для ответа на ваши вопросы также найдутся веские аргументы «Да» и «Нет».

Лично я отвечу на ваши два основных вопроса утвердительно:

Приемлемо ли использовать классы POCO (домена) на уровне пользовательского интерфейса?

Я предполагаю, что под «уровнем пользовательского интерфейса» вы на самом деле имеете в виду не часть представления шаблона MVVM, а ViewModels. (Я полагаю, большинство специалистов по MVVM возражали бы против того, чтобы представление вообще напрямую ссылалось на модель.)

Нет ничего необычного в том, чтобы обернуть POCO из вашего доменного проекта в качестве свойства в ViewModel и привязать этот обернутый POCO непосредственно к представлению. Большой плюс в том, что это просто. Вам не нужны дополнительные классы ViewModel или реплицируемые свойства в ViewModel, а затем копировать эти свойства между объектами.

Однако, если вы используете WPF, вы должны учитывать, что механизм привязки будет напрямую записывать данные в ваши свойства POCO, если вы привяжете их к представлению. Это не всегда может быть тем, что вы хотите, особенно если вы работаете с подключенными объектами и объектами с отслеживанием изменений в форме WPF. Вы должны подумать о сценариях отмены или о том, как вы восстанавливаете свойства после отмены, которые были изменены механизмом привязки.

В моем текущем проекте я работаю с отдельными объектами: я загружаю POCO со уровня данных, отделяю его от контекста, удаляю контекст, а затем работаю с этой копией в ViewModel и привязываю ее к представлению. Обновление на уровне данных происходит путем создания нового контекста, загрузки исходного объекта из базы данных по идентификатору, а затем обновления свойств из измененного POCO, который был привязан к представлению. Таким образом, проблема нежелательных изменений присоединенной сущности исчезает при таком подходе. Но есть и недостатки в работе с отдельными объектами (обновление, например, более сложное).

Ничего, если я реализую IDataErrorInfo интерфейс в сгенерированном классе POCO?

Если вы привязываете свои объекты POCO к представлению (через обертывающую ViewModel), это не только нормально, но вы даже должны реализовать IDataErrorInfo в классе POCO, если хотите использовать встроенную проверку свойств механизма привязки WPF. Хотя этот интерфейс в основном используется вместе с технологиями пользовательского интерфейса, он является частью System.ComponentModel пространства имен и, следовательно, напрямую не привязан к каким-либо пространствам имен пользовательского интерфейса. По сути, IDataErrorInfo это всего лишь простой контракт, который поддерживает отчет о состоянии объекта, который также может быть полезен вне контекста пользовательского интерфейса.

То же самое верно для INotifyPropertyChanged интерфейса, который вам также потребуется реализовать в ваших классах POCO, если вы привязываете их непосредственно к представлению.

Я часто вижу мнения, которые не согласны со мной по нескольким архитектурным причинам. Но ни одно из этих мнений не утверждает, что другой подход проще. Если вы строго хотите избежать наличия классов модели POCO в вашем слое ViewModel, вам нужно добавить еще один слой отображения с дополнительной сложностью и затратами на программирование и обслуживание. Итак, я бы проголосовал: делайте это простым, пока у вас нет убедительной причины и явной выгоды для усложнения вашей архитектуры.

Комментарии:

1. Спасибо. Много весомых моментов, особенно сценарий отмены.