Следует ли использовать агрегированные корневые репозитории DDD с EF 4.1 + LINQ?

Я читал DDD Evans и экспериментирую с дизайном совокупного корневого репозитория с использованием C # и Entity Framework 4.1 + LINQ.

Однако меня беспокоят фактические запросы, отправляемые в БД. Я использую SQL 2008 R2 и запускаю SQL Profiler, чтобы проверить, что делает БД в ответ на код LINQ.

Рассмотрим простой дизайн с двумя объектами с Person и EmailAddress. Один человек может иметь от нуля до нескольких адресов электронной почты, а адрес электронной почты должен иметь ровно одно лицо. Person - это совокупный корень, поэтому не должно быть репозитория для адресов электронной почты. Адреса электронной почты следует выбирать из репозитория Person (согласно DDD Evans).

Для сравнения, у меня есть временный репозиторий для адресов электронной почты. Следующая строка кода:

var emailString = "[email protected]";
var emailEntity = _tempEmailRepository.All.SingleOrDefault(e => 
    e.Value.Equals(emailString, StringComparison.OrdinalIgnoreCase));

... выполняет хороший чистый SQL-запрос в соответствии с профилировщиком:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]

Я могу выбрать электронную почту из репозитория людей с помощью следующего кода:

var emailEntity = _personRepository.All.SelectMany(p => p.Emails)
    .SingleOrDefault(e => e.Value.Equals(emailString, 
        StringComparison.OrdinalIgnoreCase))

Это дает мне ту же сущность во время выполнения, но с разными командами, отображаемыми в профилировщике SQL:

SELECT 
[Extent1].[Id] AS [Id],  
[Extent1].[FirstName] AS [FirstName],  
[Extent1].[LastName] AS [LastName], 
FROM [dbo].[Person] AS [Extent1]

В дополнение к вышеупомянутому запросу, который выбирает из Person, существует ряд событий «RPC: Completed», по одному для каждой строки EmailAddress в базе данных:

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=2

В моем тестовом db есть 14 строк в dbo.EmailAddress, и есть 14 различных вызовов RPC: Completed, каждый с другим значением @ EntityKeyValue1.

Я предполагаю, что это плохо для производительности SQL, поскольку по мере того, как таблица dbo.EmailAddress получает больше строк, все больше этих RPC будет вызываться в базе данных. Есть ли другой лучший подход к использованию совокупных корневых репозиториев DDD с EF 4.1 + LINQ?

Обновление: решено

Проблема заключалась в том, что свойство All возвращало IEnumerable<TEntity>. После того, как это было изменено на IQueryable<TEntity>, LINQ включился и выбрал все лицо + электронные письма одним выстрелом. Однако мне пришлось связать .Include (p => p.Emails), прежде чем возвращать IQueryable из All.


person danludwig    schedule 18.05.2011    source источник
comment
Что вы получите от All собственности?   -  person Ladislav Mrnka    schedule 18.05.2011
comment
Хороший вопрос. Когда я разместил это, Все возвращали IEnumerable ‹TEntity›. Поменял его на IQueryable и увидел разницу. Выложу обновление.   -  person danludwig    schedule 18.05.2011


Ответы (1)


Учитывая уровень абстракции, который вам уже дают современные ORM, я лично не советовал бы добавлять дополнительный уровень абстракции между вами и вашей базой данных. Помимо изобретения колеса, вы обнаружите, что использование выбранной ORM непосредственно на уровне обслуживания даст вам более точный контроль над запросами, стратегиями выборки и кэширования.

Серия Айенде Wages of Sin - хороший ресурс для различных других аргументов против использования спецификаций / репозиториев с современной ORM, особенно с учетом того, что LINQ уже фактически дает вам почти все, что вам может понадобиться.

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

Репозиторий и Спецификация - это шаблон Инфраструктура. Инфраструктура служит цели, а не сама по себе. Когда дело доходит до инфраструктуры, я рекомендую строго применять Принцип повторного использования абстракции. Вкратце, RAP говорит, что вам следует вводить абстракцию тогда и только тогда, когда она будет использоваться более чем двумя потребителями, и этот дополнительный уровень абстракции фактически реализует некоторое поведение. Если вы вводите абстракцию только для того, чтобы отделить вас от чего-то (например, ORM), будьте очень осторожны, вполне вероятно, что вы получите дырявую абстракцию.

Весь смысл DDD состоит в том, чтобы отделить вашу модель предметной области от вашей инфраструктуры и сделать вашу модель предметной области как можно более выразительной. Нет никаких доказательств того, что этого нельзя достичь без использования репозиториев. Репозитории нужны только для того, чтобы скрыть детали доступа к данным, что уже делает ORM. (Кстати, учитывая возраст книги DDD, я не думаю, что тогда распространенное использование ORM было на картинке). Теперь репозитории могут быть полезны для принудительного применения агрегированных корней и т. Д. Однако я думаю, что это следует рассматривать, проводя четкое различие между операциями «чтения» (запросы) и операциями «записи» (команды). Только для последнего модель предметной области должна быть актуальной, запросы часто лучше обслуживаются специализированными (и более гибкими) моделями (такими как DTO или анонимные объекты).

Случай для спецификаций аналогичен. Назначение спецификаций аналогично. Их сила заключается в создании элементов предметно-ориентированного языка для запросов к объектам. С появлением LINQ большая часть «клея», который предоставляет обобщенные шаблоны спецификаций для объединения этих элементов, устарела. Подсказка: взгляните на Predicate Builder (‹50 строк C #), вероятно, это все, что вам понадобится для реализации спецификаций.

Подводя итог этому длинному (и, надеюсь, не слишком дезорганизованному, я вернусь к нему позже, надеюсь):

  1. Не сходите с ума по поводу инфраструктуры, создавайте ее на ходу.
  2. Используйте модель предметной области для поведения, специфичного для предметной области, а не для поддержки ваших взглядов.
  3. Сосредоточьтесь на более важной части DDD: используйте агрегированные корни, создайте универсальный язык, обеспечьте хорошее общение с бизнес-экспертами.
person Johannes Rudolph    schedule 18.05.2011
comment
Я прочитал книгу на прошлой неделе, спасибо за чистые слова / советы. Думаю, мне нужно перечитать книгу с другой стороны - person Khh; 19.05.2011
comment
Все отличные моменты для обсуждения. В нашем случае я думаю, что шаблон репозитория все еще оправдан: при редактировании данных в некоторых сущностях исходные данные должны сохраняться в БД. Таким образом, любая конкретная сущность может иметь несколько ревизий, только 1 из которых является текущей версией. Было бы нарушением SRP обременять уровень сервиса этими проблемами, поэтому мы применили репозитории супертип + уровня сущности (+ UoW) для их инкапсуляции. Теперь цель состоит в том, чтобы репозитории были как можно более тонкими, легкими и минимальными. Пойду сейчас прочитаю эти статьи «Возмездие за грех» ... - person danludwig; 20.05.2011
comment
Я бы не согласился. Попробуйте смоделировать простой случай. Этот объект никогда не должен иметь свойство 'name', установленное на 'John' в ORM. Мне еще предстоит найти правдоподобное решение с EF или NHibernate или чем-то еще. - person Andriy Drozdyuk; 29.07.2013