Должен ли я использовать агрегированные корневые репозитории DDD с EF 4.1 + LINQ? - PullRequest
5 голосов
/ 18 мая 2011

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

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

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

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

var emailString = "someone@somewhere.com";
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 Profiler:

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

В дополнение к вышеуказанному запросу, который выбираетот Person есть несколько событий "RPC: Завершено", по одному для каждой строки 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

Мой тестовый БД содержит 14 строк в dbo.EmailAddress, и существует 14 различных RPC: Завершенные вызовы, каждый из которых имеет свое значение @ EntityKeyValue1.

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

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

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

1 Ответ

12 голосов
/ 19 мая 2011

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

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

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

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

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

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

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

  1. Не сходите с ума по инфраструктуре, создавайте ее по ходу работы.
  2. Используйте модель вашего домена для поведения, специфичного для домена, а не для поддержки ваших представлений.
  3. Сосредоточьтесь на более важной части DDD: используйте совокупные корни, создайте свой вездесущий язык, обеспечьте хорошее общение с бизнес-экспертами.
...