Pull to refresh

Comments 269

Не понял про DateTimeOffset, в PostgreSQL. PostgreSQL хранит временную зону.
По дефолту вроде нет. И timestamp и date хранят в localdatetime. В оракле кстати тоже самое.
Вообще если есть проблемы с временем, то можно всегда сохранить epoch time как long, т.к он везде одинаковый)
Мы используем тип timestamp with time zone. Postgres хранит в UTC, а перед выдачу клиенту преобразует в локальное.
в MySQL также в timestamp хранится время по Гринвичу и преобразуется перед выдачей клиенту.
в SQLite та же беда, кто просит преобразовывать?
Администратор, настраивая часовую зону.
Не у всех есть администратор
Если нам нужны одни даты в UTC а другие в Local, БД должна возвращать ту дату, которая в ней храниться, а не преобразовывать
Человек, выполняющий его функцию, есть всегда. Кто-то устанавливает, кто-то настраивает.

Кому БД должна? БД должна хранить и возвращать так, как указано в её документации.
inwake, спасибо за статью! На мой взгляд, эту тему нужно периодически поднимать, чтобы те, кто рассматривает возможность использования EF или других ORM в первый раз, понимали, с какими ограничениями они столкнутся в будущем, и учитывали это в своих проектных решениях. Как говорит Мартин Фаулер:
Часто мне кажется, что в большой степени разочарование ORM связано с раздутыми ожиданиями
Хотелось бы добавить к сказанному еще несколько моментов.
  1. Почти все сказанное в статье, по-моему, можно отнести ко всем ORM, поскольку все занимаются связыванием двух разных моделей представлений: объектно-ориентированной и реляционной, т.е. как-то решают вопрос с уже набившим оскомину object-relational impedance mismatch.
  2. Проблемы с производительностью могут возникать не только при выполнении одного LINQ-запроса на разных провайдерах, но и при попытке выполнить сложные запросы на одном провайдере. Например, я имел опыт написания LINQ-запроса с группировкой данных, в качестве СУБД выступал SQL Server. Я понимал, что EF построит мой запрос не оптимально, но на первых порах я не хотел заниматься оптимизацией. В итоге, проблема проявилась гораздо раньше, чем я ее ждал: получилось что при наличии 800 записей в основной таблице с данными запрос выполнялся 8 секунд, а на следующий день, когда записей в таблице стало 1200 — 14 секунд. Как выяснилось, запрос, который строил EF, содержал 8 вложенных select-выражений! В итоге тот же запрос был написан на чистом SQL с использованием только двух вложенных select-ов и все стало выполняться в пределах одной секунды.
  3. EF предоставляет нам репозиторий с максимально универсальным поисковым интерфейсом — IQueryable. И именно потому, что количество запросов, построенных с его помощью, безгранично, то разработчики конкретных LINQ-провайдеров вынуждены как-то ограничивать это множество. В итоге, мы действительно получаем то, что при работе с конкретным провайдером нужно понимать, что он поддерживает, а что нет. Т.е. особенности реализации провайдера «протекают» через абстракцию IQueryable. У Mark Seemann есть хорошая статья о том, что не нужно использовать IQueryable в интерфейсах репозитория. Хоть она и несколько категорична, почитать ее стоит.
    Разрабатывая собственный репозиторий со специализированным интерфейсом, мы в силах ограничить число возможных запросов к нему и, соответственно, обеспечить более полную реализацию.Но проблема может возникнуть тогда, когда мы должны создать достаточно универсальный репозиторий, который может хранить различные классы объектов и допускать достаточно сложные запросы. И тут нам все-равно придется либо вводить свой универсальный запросный язык, либо использовать IQueryable, ограничив набор поддерживаемых запросов и доведя это до тех, кто будет наш репозиторий использовать.
Как выяснилось, запрос, который строил EF, содержал 8 вложенных select-выражений!

Во-первых сам EF ничего не строит сам, он только интерпретирует ваш запрос.
Во-вторых сами по себе 8 вложенных select никакого влияния на быстродействие не оказывают.

Вообще вложенный select в запросе может быть как correlated subquery, так и derived table. Derived table вообще никак не влияет на быстродействие, в плане все проекции будут спущены на самый нижний уровень. Correlated subquery может приводить к проблемам быстродействия. НО EF сам нигде correlated subquery не генерирует. Надо постараться, чтобы так случилось.

То есть проблема не в том, что EF плохой, а в том, что вы не смогли родить на нем нормальный запрос.

Кстати вы могли еще нарваться на баг с Navigation Properties, его просто надо знать и обходить.
Во-первых сам EF ничего не строит сам, он только интерпретирует ваш запрос
Конечно. Но только интерпретировать он его может по-разному. И получается, что помимо LINQ я должен понимать особенности интерпретации expression tree с запросом каждым конкретным провайдером. Т.е. абстракция уже не скрывает от меня детали реализации, а значит она «течет».
То есть проблема не в том, что EF плохой
Я не говорил, что EF плохой! EF отличный, просто нужно понимать, что он не избавляет от необходимости хорошо понимать его работу с СУБД. Вы, кстати, наглядно аргументировали это в своем комментарии.
И получается, что помимо LINQ я должен понимать особенности интерпретации expression tree с запросом каждым конкретным провайдером.

Конечно. Для оптимизации быстродействия всегда надо знать что происходит на два уровня ниже твоего уровня абстракции. А еще и на один выше.

абстракция уже не скрывает от меня детали реализации, а значит она «течет»

Любая нетривиальная абстракция течет. Это нормально.
Отлично! Я ведь с этого и начал:
… чтобы те, кто рассматривает возможность использования EF или других ORM в первый раз, понимали, с какими ограничениями они столкнутся в будущем, и учитывали это в своих проектных решениях
И? Я же писал о том, что проблемы с производительностью это не проблемы EF. Он ничего не делает чтобы ухудшить производительность. Все делают программисты. И репозиторий тут не поможет, а скорее хуже сделает.
Как-то Вы мой пример, который приведен лишь для того, чтобы обратить внимание на возможные последствия (чаще всего, по незнанию того, «что происходит на два уровня ниже твоего уровня абстракции»), вырвали из контекста и за него меня «хлещете». Он не направлен на то, чтобы как-то упрекнуть EF. Это как раз пример того, как непонимание механизмов внутренней работы (в данном случае мое) может привести к проблеме.
А чем тогда репозиторий поможет если вы механизмов работы не понимаете?
А где я сказал, что репозиторий поможет решить эти проблемы?
Похоже я в своем первом посте выразил слишком общее одобрение всему сказанному в статье. Моя вина, каюсь! Но попробую пояснить: подавляющая часть статьи содержит описание проблем, с которыми можно столкнуться при, скажем так, использовании EF «в лоб», без должного понимания. Именно к этому описанию я и попытался добавить некоторое дополнение.
А вот это высказывание я поддержать не готов:
я говорю да Repository, ведь все описанные проблемы легко обойти с помощью этой абстракции которая обернет EF и поглотит проблемы совместимости
Оно вообще похоже на то, что найдена «серебряная пуля». Но мне кажется, что здесь свое дело сделали эмоции автора, ведь и стиль изложения материала в статье несколько «импульсивный», имхо. Насчет Repository над EF не хочу писать обрывками: думаю, идея полезная и может решить часть проблем, но каких и как — нужно пояснить. Попробую сформулировать это чуть позже.
А ведь именно это высказывание является обобщением статьи и месседжем автора.
Пришлось перечитать несколько раз. Автору стоило бы как-то выделить, на мой взгляд. Просто человеку ведь свойственно выделять из множества информации ту, которая его беспокоит больше. Вот так же вышло и со мной.
Да, и я не отказываюсь, от своих слов. Описанные проблемы он решит, добавит новых? может и такое быть
Описанные проблемы можно решить и без добавления новых. Так что полезность repository сильно преувеличена.
я пока не увидел ни 1 нормального решения, кроме «Репозиторий это плохо, нужно писать, расходы, автор врет»
Решения чего? Я уже объяснил, что нет смысла городить репозитории, берите ваши QueryBulder_ы и инжектите их в БЛ. Все, репозитории не нужны.
а много раз говорил, что выставление наружу контекста это плохо. описанве здемь проблемы
IQuariable — не контролируемые запросы
ни кем, синтаксически можна написать сколь угодно сложные выражения, которые не возможно перевести в sql, и об этом узнается только при исполнении.
1) А кто помешает программисту сделать тоже самое внутри репозитария?
2) Кто помешает написать запрос любой степени сложности для IEnumerable, который отдается из репозитария и получить тормоза?
3) Что лучше — программа которая работает, но дико тормозит, или программа, которая падает при тестировании?

Обычно один и тот же программист пишет всю логику. Поэтому ему в любом случае надо уметь писать запросы к EF.
Мы уже обсудили, что репозиторий не решает описанные проблемы сам по себе, он просто их переносит в другие части системы.
Repository — это фасад, фасад ничего никогда не решает
Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.
Правда? Так зачем же вы пишете «ведь все описанные проблемы легко обойти с помощью этой абстракции которая обернет EF и поглотит проблемы совместимости»?
Репозиторий поможет обойти непонятные механизмы чтобы получить тот SQL-запрос, который хочется в данном конкретном случае. Или поможет обойти механизмы, которые понятно что такой запрос сгенерировать не могут.
Каким образом? Если репозиторий вызывает тот же EF, то другой запрос вы не получите. Если вы рассматриваете репозиторий вместо EF, то сразу бросьте это занятие.
Спасибо за поддержку, похоже эта идея не популярна.
Просто это немного другая идея. Это идея о том, что вообще парадигма ORM (лежащая в основе EF и любого другого решения, хранящего объектные данные в реляционной структуре) обладает внутренними противоречиями, которые не снимаются ни репозиторием, ни чем-либо еще. Фаулер, на самом деле, просто предлагает рассматривать и нереляционные хранилища, поскольку зачастую они оказываются выигрышнее.
Никаких внутренних противоречий у ORM нет (если понимать его именно как маппер данных на языковые конструкции). Есть внешние противоречия. ОО-программисты хотят работать с данными как с графом объектов, а поднимать из базы этот граф, отслеживать изменения и сохранять — очень накладно. В нереляционных хранилищах каждый граф объектов и есть сущность, которая поднимается из базы и сохраняется целиком. Вот только нереляционные хранилища гораздо менее приспособлены для хранения данных, чем реляционные субд.
Вот собственно сама идея «мы хотим работать с данными как с графом объектов, но хранить их в реляционной БД» — а именно она лежит в основе ORM — и содержит внутренние противоречия, которые вы же прекрасно и описали.
Так ORM тут не при чем. Мапинг данных на объекты на основе метаданных вовсе не требует сводить всю логику к операциям на графе объектов (domain model).
Бррр, а какое отношение имеет сведение логики к операциям к обсуждаемому вопросу? Проблемы ORM как подхода именно в том, что он пытается сделать взаимное преобразование двух плохо сочетающихся парадигм, причем в идеале сделать это преобразование максимально незаметно для потребителя — и на этом и гибнет.

Это, однако, не означает, что ORM лишен смысла, или что этот недостаток фатален — просто надо осознавать эту особенность (O-R impedance mismatch) при выборе технологий и подходов.
Проблемы создает именно попытка бизнес-логику описать в терминах операций с графом объектов (классов с методами), а не сам мэпинг.

сделать это преобразование максимально незаметно для потребителя

Если отказаться от этой идеи, то ORM начинает прекрасно работать.
Кстати, обратите внимание, что в статье, на которую вы ссылаетесь, Симан пишет весьма занятную вещь, несколько, скажем так… подрывающую идею поста:

If you have a specific ORM in mind, then be explicit about it. Don't hide it behind an interface. It creates the illusion that you can replace one implementation with another. In practice, that's impossible.


Ведь идея поста сформулирована вот так:

я говорю да Repository, ведь все описанные проблемы легко обойти с помощью этой абстракции которая обернет EF и поглотит проблемы совместимости.
Вы увидели свет в конце тоннеля, луч надежды, но не заметили что он не для вас, тк читаете между строк.
If you have a specific ORM in mind
да это так, меня очень волнует как конкретная ОРМ обрабатывает мои запросы.
Don't hide it behind an interface. It creates the illusion that you can replace one implementation with another. In practice, that's impossible.
«Не скрывайте ее за интерфейсом», да это то о чем пишу я, общий IQuariable это большая проблема. «Это создает иллюзию, что можно заменить одну реализацию другой» именно об этом я и пишу, нельзя один провайдер заменить другим и думать что это прокатит. Заметьте у меня в голове Repository который знает о проблемах конкретных провайдеров и борется только с ними а не с общими проблемами ОРМ.
Тогда зачем вы скрываете конкретную ORM (EF) за интерфейcом (репозиторием)?
да интерфейс общий, но используется ограниченный набор запросов, отражающий запросы предметной области, и для меня важно что изменения в запросах к одной бд не поломают запросы к другим, тк переводом запросы предметной области переводятся в запросы к конкретной ОРМ, конкретной реализацией репозитория.
используется ограниченный набор запросов

То есть вы не используете expression trees от LINQ, а сделали свой интерфейс запросов?
expression trees ведет к описанной проблеме
Ага, то есть вы используете свой интерфейс запросов. Собственно, это и есть то следствие выбранного вами подхода о котором вы умалчиваете в вашем посте: теперь вам либо придется реализовать развитый собственный язык запросов (и тогда еще не факт, что вы не столкнетесь с теми же проблемами, что и выше), либо вы радикально урежете доступную коду-потребителю функциональность. Потому-то репозиторий и не решает проблемы, которую создает LINQ — он делает вид, что ее не существует, потому что он просто не дает пользователю таких возможностей.
Доступно все, что описано в предметной области, это ограждает от невалидных и неэффективных запросов, дублирования и головной боли по совместимости при нескольких бд.
Доступно все, что описано в предметной области, это ограждает от невалидных и неэффективных запросов

Вы серьезно? Вот у вас есть предметная область с типом «документ», у которого есть заголовок и тело, и то, и другое — строка. Чем определяется, в каком из двух полей можно делать полнотекстовый поиск, а в каком — нет?
предметной областью, то что в предметке есть такой документ, ничего не говорит о том как можно с ним взаимодействовать, исходя из описания поиск можно вести по любому из полей, а почему нет?
Конечно, можно. Только поиск по заголовку можно делать стандартным для реляционных БД способом, а по телу — нет (потому что оно слишком большое, и поиск перебором становится вечным). Предметной области на это наплевать, поэтому от неэффективных запросов она вас не защитит.
да ладно, если это документы справки и текста в них не более 150 символов, почему нет? это конкретная предметная область.
Вот я вам и даю конкретную предметную область, где документы — это архив на 6+ Tb, а тело каждого документа превышает десятки тысяч символов. При этом, что характерно, пользователю нужен поиск по телу.
изначально вы об этом не упомянули, что вы подразумеваете под
можно делать стандартным для реляционных БД способом
что вы подразумеваете под «можно делать стандартным для реляционных БД способом»

LIKE '%term%'
а какой способ вы предлагаете для оьхода данной проблемы?
А это, очевидно, зависит от доступного инструментария. В данном случае я просто хотел вам продемонстрировать, что подход «доступно все, что есть в предметной области» не «ограждает от неэффективных запросов».
как раз наоборот, если для предметной области нужен поиск по строке, то лучше всего подойдет хранить тела документов в файлах, отфильтровать документы из бд, получить расположение файлов, провести поиск в них, эт овсе можно скрыть все тем же репозиторием
Вы, похоже, вообще не следите за тем, что вам показывают. Жаль.
Вы написали:

Доступно все, что описано в предметной области, это ограждает от невалидных и неэффективных запросов,


Я вам продемонстрировал, как подход «доступно все, что описано в предметной области» не ограждает от неэффективных запросов.
Совсем необязательно урезать доступную коду-потребителю функциональность. Просто в интерфейсе репозитория можно оставить (унаследовать или проксировать) стандартный интерфейс, а для критичных запросов иметь отдельные. Или реализовать оптимизацию запросов под конкретного провайдера как декоратор — перед выполнением запроса проверять его на принадлежность к некоему множеству и выполнять «нативный» код, если принадлежит к нему, а если нет, то передавать стандартному.
Не обязательно. Но автор статьи пошел именно таким путем.
Совсем необязательно урезать доступную коду-потребителю функциональность
я согласен
а для критичных запросов иметь отдельные
а как узнать какой запрос критический если у нас «доступную коду-потребителю функциональность» есть доступ и тут можно пилить что угодно и как угодно.
Ничего не напоминает? Это реализация Repository + UoW. [...] Основная идея EF ( как и любого Repository )...

EF — это не репозиторий. В терминах Фаулера (PoEAA), EntityFramework — это Data Mapper. Репозиторий — это следующий уровень (в сторону домена), и его задачи отличаются от задач data mapper.

я говорю да Repository, ведь все описанные проблемы легко обойти с помощью этой абстракции

Посмотрим внимательнее на некоторые описанные вами проблемы.

Вот тут самое интересное, под разные провайдеры нужно писать свои запросы на LINQ. Еще интересный сценарий: написали некоторый функционал с кучей запросов к контексту и все прекрасно работает. Но приложение cross и нужно проверить, как это все будет работать на другом провайдере. Было обнаружено, что часть запросов стали не эффективные. Не беда – оптимизация. Все хорошо. Вопрос как оптимизация отразилась на работе при исходном провайдере?

И как же репозиторий позволяет обойти эти проблемы? Вероятно, вы имеете в виду, что вы напишете две разных реализации репозитория для двух разных провайдеров. Это здорово, но вы помните, что вы только что увеличили свои затраты вдвое?

Ограничения запроса

В EF разработчик может сформировать запросы, используя или LINQ to Entities, или Entity SQL или их комбинацию. В любом случае такой запрос преобразовывается механизмом EF в промежуточное представление как expression tree и отправляется в EF-провайдер, чтобы он сгенерировал SQL запрос. Так как EF была первоначально разработана для SQL Server, то иногда EF-провайдеры должны значительно преобразовать первоначальное expression tree, чтобы сгенерировать корректный SQL для определенной базы данных. Однако это не всегда возможно.


Здесь ясно сказано mission impossible. SQL SQLю рознь, провайдер провайдеру. Тут даже речь не о том, что запрос будет не эффективен, а о том, что он может покрошить выполнение приложения.


Во-первых, вы передергиваете: нигде не сказано, что эта задача не имеет решения; сказано лишь что не всегда возможно сгенерировать корректный SQL для всех БД для любого AST. Но предположим на минуту, что вы правы, и эта задача действительно не имеет решения. Как же репозиторий ее решает в таком случае?
>>EF — это не репозиторий.
Ну чем же DbContext не репозиторий? Классик, который наружу выставляет коллекции _доменных_ объектов с которыми можно работать как с обычнми коллекциями + немного UoW.
Repository replaces specialized finder methods on Data Mapper classes with a specification-based approach to object selection. Compare that with the direct use of Query Object, in which client code may construct a criteria object (a simple example of the specification pattern), add() that directly to the Query Object, and execute the query. With a Repository, client code constructs the criteria and then passes them to the Repository, asking it to select those of its object that match. From client code's perspective, there's no notion of query «execution»; rather there's the selection of appropriate objects through the «satisfaction» of the query's specification. This may seem an academic distinction, but it illustrates the declarative flavor of object interaction with Repository, which is a large part of its conceptual power. [PoEAA, 2003, pp. 323-324]
С другой стороны там же написано [PoEAA, 2003]:
A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer.
И если рассматривать в качестве реализации data-mapper только часть EF, то я не вижу каких-то особенных противоречий в утверждении, что EF реализует в том числе и шаблон Repository. Ведь и в приведенной Вами цитате сказано, что:
...This may seem an academic distinction, but it illustrates the declarative flavor of object interaction with Repository, which is a large part of its conceptual power.
Т.е. концептуально EF конечно не репозиторий, поскольку репозиторий более окрашен в «тона предметной области». Так же, как и Specification по сравнению с Query Object, как мне кажется. С другой стороны, формально фасад EF очень похож на описание фасада репозитория.
Некоторые интересные мысли по поводу EF и Repository можно найти в этой дискуссии на stackexchange.com.
Т.е. концептуально EF конечно не репозиторий, поскольку репозиторий более окрашен в «тона предметной области». Так же, как и Specification по сравнению с Query Object, как мне кажется.

Именно так. Я считаю это разделение очень важным, поскольку оно позволяет точнее определять ответственности в сложной системе.

С другой стороны, формально фасад EF очень похож на описание фасада репозитория.

Что именно вы считаете фасадом EF?
Что именно вы считаете фасадом EF?
В данном случае я имею ввиду DbSet<T>, который, реализуя IQueryable<T> и добавляя Add и Remove, фактически и действует как
… in-memory domain object collection
Проблема в том, что там еще есть SaveChanges. И вот тут вся эта конструкция превращается в UoW, и начинается путаница, которой лучше бы избегать.
В общем да. Но только как в случае с созданным поверх EF репозиторием реализовать тот же UoW, не внося путаницы уже в наш репозиторий?
А это вопрос на пять, который как раз и является иллюстрацией того, почему репозиторий поверх EF строить не всегда удобно.
Создать транзакцию и раздать ее репозиториям. Конкретная реализация UOWScope, будет подтверждать или отменять.
Свою транзакцию или из System.Transactions?
Мы используем BuisnessTransaction (посути обертка для наших нужд над системной)
То есть внутри у вас системная? А как вы гарантируете поддержку всеми репозиториями этой системной транзакции (учитывая, что даже у EF с этим есть… некоторые проблемы, скажем так)?
А как вы гарантируете поддержку всеми репозиториями этой системной транзакции

вы наверное ошиблись, скорее всего речь о провайдере?
Я вряд ли ошибся, скорее, кто-то из нас кого-то неправильно понял. Кто определяет границы транзакции — репозиторий или использующий его код?
Границы транзакции определяет UoW только он, и не кто больше.
UoW находится снаружи репозитория?
В таком случае он ничего не знает о деталях репозиториев (и о наличии внутри них каких-либо провайдеров). Как следствие, с его точки зрения, все репозитории должны поддерживать транзакции из System.Transactions (ну или вашу транзакцию, что, в принципе, одинаково). Как вы это гарантируете?
Гарантировать это ни как нельзя, поэтому мы и используем обертки для контроля, закрепление данных происходит только при сохранении, идет отслеживание UoWscope, по сотоянию которых ясно, нужно ли сохранять изменения или нет.
А как обертка может отследить, что происходит внутри репозитория? Или вы выставляете статус изменений наружу?
наружу, это один из вариантов, но это захламляет объекты, для простых свойст при сохранении в маппинге происходит обновление всех полей, тут нужно отдать должное ChangeTracker отметить измененными только те свойства, которые были изменены.
А, привет инкапсуляции. В таком случае можно вообще что угодно сделать, только хорошей архитектурой это уже называть стоит с осторожностью.
к сожалению везде нужно идти на компромиссы.
Тогда все же хочется понять Ваш подход.
Пусть мы имеем РСУБД, даже конкретно SQL Server, и храним в ней данные модели предметной области. Для точности пусть это будут DDD-агрегаты типа Foo с какой-то внутренней структурой. Для маппинга используем EF и получаем объекты Foo через DbSet<Foo>.
В то же время обобщенный интерфейс DBSet-ов нас не устраивает в том плане, что в разных местах системы, где он используется будет «зверинец» LINQ-запросов, в том числе и повторяющихся. Логично, вроде бы, в этом случае ввести над DbSet<Foo> какой-то репозиторий FooRepo, в интерфейсе которого будет ограниченное число методов доступа к данным, которых достаточно для работы всего вышестоящего кода в системе. Но в то же время нам нужен UoW, чтобы мы могли модифицировать объекты группам и рамках одной транзакции. Как добавить этот UoW?
Как бы Вы подошли к решению такой задачи?
Решение, которое я реально использовал в практике — это выставить все необходимые методы прямо на DbContext, и сделать из них интерфейс (тем самым ограничив доступ). UoW в таком случае проще всего реализовать, просто выставив SaveChanges в публичном интерфейсе.

В этом сценарии, правда, надо очень четко следить за жизненным циклом контекста, чтобы в нем не было «лишних» изменений, которые провалятся в БД незаметно для программиста. Идеальный вариант — открыть контекст по месту использования, внести нужные изменения, сохранить, закрыть.
Т.е. получается что-то вроде:
public class FooRepo {
  private FooDbContext _dbContext;

  public FooRepo() {
     _dbContext = new FooDbContext();
  }
  public GetFooByParamSet1(paramSet1) { ... }
  public GetFooByParamSet2(paramSet2) { ... }
  ...
  public AddFoo() {}
  public RemoveFoo() {}
  ...
  public SaveChanges() {}
}

Если я правильно понимаю, получается обертка над DbContext-ом, которая одновременно и UoW и Репозиторий?
Намного проще.

public interface IFooDbContext {
  GetFooByParamSet1(paramSet1);
  GetFooByParamSet2(paramSet2);
  AddFoo();
  RemoveFoo();
  SaveChanges();
}

class FooDbContext: DbContext, IFooDbContext {
  public GetFooByParamSet1(paramSet1) { ... }
  public GetFooByParamSet2(paramSet2) { ... }
  ...
  public AddFoo() {}
  public RemoveFoo() {}
  ...
  public SaveChanges() {}
}
Мне почему-то кажется, что это не намного проще. Если я правильно понимаю, это замена композиции наследованием? И здесь меня смущают следующие моменты.
  1. Ограничение доступа к DbContext-у очень условное: т.е. можно конечно коду-потребителю передавать IFooDbContext и там будет только то, что мы туда внесли. Но ведь пытливый разработчик может всегда привести его к типу FooDbContext и получить тот же DbSet и снова начать плодить «зверинец» разнообразных запросов. Конечно, в случае замены наследования композицией можно тоже взять Reflection и сделать все, что хочется, но это уже явный «хак», на который просто так никто не решится.
  2. Что если мы захотим написать unit tests к методам GetFooByParamSet1 и т.д.? Как мы заменим сам DbContext каким-нибудь mock-объектом?
Нет, это не замена композиции наследованием. Это сохранение функциональности в изначально ответственной за нее сущности. Я напомню, что использование наследника DbContext — это стандартный шаблон в EF. Путь развития приблизительно такой:

1. Сначала делаем «как в книжке», выставляя сеты на каждую сущность:
class FooDbContext: DbContext
{
  public DbSet<A> A {get;}
  public DbSet<B> B {get;}
}


2. Затем извлекаем интерфейс, чтобы можно было спокойно тестировать зависимые объекты:
interface IFooDbContext
{
  DbSet<A> A {get;}
  DbSet<B> B {get;}
}


3. Понимаем, что интерфейс отдает слишком конкретные реализации, которые неудобно тестировать, и которые при этом никому не нужны:
interface IFooDbContext
{
  IQueryable<A> A {get;}
  IQueryable<B> B {get;}
}


4. В качестве бесплатного бонуса получаем возможность внутри подменить IQueryable на что угодно. При этом важно то, что для потребителей это как был интерфейс контекста данных, так и остался, просто он стал чуть более обобщенным. Мы не порождаем никаких новых сущностей и новых слоев.

Ограничение доступа к DbContext-у очень условное: т.е. можно конечно коду-потребителю передавать IFooDbContext и там будет только то, что мы туда внесли. Но ведь пытливый разработчик может всегда привести его к типу FooDbContext и получить тот же DbSet и снова начать плодить «зверинец» разнообразных запросов. Конечно, в случае замены наследования композицией можно тоже взять Reflection и сделать все, что хочется, но это уже явный «хак», на который просто так никто не решится.

Вы-первых, вы совершенно зря думаете, что если человек решился на обратное приведение, то он не решится на reflection. От злонамеренного программиста вы не защититесь все равно, наша задача состоит в том, чтобы упростить работу нормальному.
Во-вторых, обратное приведение не проходит unit-тесты.
Наконец, в-третьих, я обычно выношу весь DAL в отдельную сборку, где интерфейс публичный, а контекст — internal.

Что если мы захотим написать unit tests к методам GetFooByParamSet1 и т.д.? Как мы заменим сам DbContext каким-нибудь mock-объектом?

Точно так же, как если вы захотите написать тесты на Set в DbContext — никак. Какой-то из уровней абстракции тестировать уже не нужно. Если вы хотите написать тесты на этот уровень, и при этом это реально оправданно, а не блажь, значит, у вас ошибка в разделении обязанностей.

Я правильно понимаю, что через интерфейс IFooDbContext вы отдаете потребителям IQueryable<A> и IQueryable<B>?
interface IFooDbContext
{
  IQueryable<A> A {get;}
  IQueryable<B> B {get;}
}
Если так, то как мы решаем проблему «зверинца» запросов от потребителей? Хочется ведь их свести вместе и ограничить-таки интерфейс DbContext-а.
Интерфейс мы уже ограничили — наружу выставлено только то, что нам надо. А унификация запросов, если это необходимо, делается либо через методы-расширения на IQueryable, либо через построители для expression.
Выставить наружу IQueryable это довольно слабое ограничение. Множество всевозможных запросов, построенных с его помощью, со временем может очень вырасти. Унификация с помощью методов-расширений к IQueryable, как я понимаю, не ограничит разнообразие запросов: ведь я по-прежнему могу писать запросы на «чистом» LINQ. А насчет «построителя для expression» что имеется ввиду? И главное, как это ограничит для потребителей возможность писать чистые LINQ-запросы?
А меня не пугает множество запросов. До тех пор, пока они покрывают бизнес-требования, по большому счету, все равно, как они написаны. Как следствие, у меня нет задачи отобрать у пользователя возможность работать с LINQ (более того, у меня как раз есть задача ее сохранить, потому что тогда я упрощу интеграцию с кучей полезных вещей), у меня есть задача упростить его работу в типовых сценариях.
Тогда получается, что если мы используем EF в качестве ORM (или, кстати говоря, C# LINQ driver над MongoDb), то репозиторий и не нужен? Или есть все-таки какие-то условия, при которых нам стоит ввести репозиторий и скрыть ORM и в том числе IQueryable?
Да, именно так и получается. Именно об этом пишет, например, Симан в статье, на которую вы же и ссылаетесь: если вы используете конкретный ORM, и не планируете использовать их несколько (а это, будем честными, очень редкая ситуация) — не надо пытаться этот ORM скрыть за другим паттерном.
Понятно. Но насчет Симана я не соглашусь: по-моему Вы отделяете приведенное Вами ранее высказывание от контекста.
If you have a specific ORM in mind, then be explicit about it. Don't hide it behind an interface. It creates the illusion that you can replace one implementation with another. In practice, that's impossible. Imagine attempting to provide an implementation over an Event Store.
Во-первых, статья называется «IQueryable is Tight Coupling» и весь ее посыл в том, чтобы не использовать этот интерфейс в своем API. Мне даже кажется, что Симан считает, что IQueryable вообще не стоило разрабатывать, поскольку абстракция «дырявая» до неприличия. Хотя непонятно, как тогда стоило бы поступить.
Во-вторых, в приведенной цитате я бы выделил фразу «It creates the illusion that you can replace one implementation with another». Возникает вопрос: implementation of what? Мне кажется, что речь об интерфейсе IQueryable.
В-третьих, в подтверждение этого есть комментарий к статье с просьбой к Марку оценить интерфейс репозитория над NHibernate:
What do you think about the API outlined in this post: http://stackoverflow.com/a/9943907/572644
И вот ответ Марка:
Looks good, Daniel. I'd love to hear your experience with this once you've tried it on for some time
По ссылке на StackOverflow есть пример кода, где IQueriable скрыт за своим интерфейсом, который уже не предоставляет IQueryable.

Только это уже какая-то герменевтика пошла…
Во-первых, статья называется «IQueryable is Tight Coupling» и весь ее посыл в том, чтобы не использовать этот интерфейс в своем API.

Нет, ее посыл в том, что IQueryable — это текущая абстракция, которая не предоставляет изоляции. Если это осознавать, равно как и осознавать ограничения этого — все будет хорошо.

Возникает вопрос: implementation of what? Мне кажется, что речь об интерфейсе IQueryable.

Вам кажется. На самом деле, речь идет о замене ORM за абстрактным интерфейсом (это, на самом деле, приведет и к замене реализации IQueryable, но это побочный эффект).

В-третьих, в подтверждение этого есть комментарий к статье с просьбой к Марку оценить интерфейс репозитория над NHibernate:

Важный пойнт оттуда: «The nice thing about this is that the business layer is completely free of querying logic and thus the data store can be switched easily.»

Если вы предполагаете заменять хранилище — тогда зависимость от IQueryable плоха (потому что его реализации не взаимозаменяемы). Если нет — то нет.

В каком-то смысле, на эту проблему можно смотреть так: представьте себе, что у вас нет IQueryable, а есть IQueryableEF, IQueryableL2S, IQueryableNH и так далее. Когда вы принимаете решение выставить такой интерфейс — вы принимаете решение «я не буду заменять EF на NH». Если это решение вас устраивает, то ничего плохого в этом интерфейсе нет.

(Ну и у меня есть отдельные замечания к описанному на SO, но они во многом уже озвучены здесь в посте: как только мы отбираем IQueryable, мы берем на себя обязанность предоставить замену. Банальный пример: как в предложенном DSL выразить запрос на (а) начатые и незавершенные (б) начатые или незавершенные? А проекции? А джойны? Ну и понеслась. А интеграция с UI и сервисной моделью навроде OData?)
Нет, ее посыл в том, что IQueryable — это текущая абстракция, которая не предоставляет изоляции. Если это осознавать, равно как и осознавать ограничения этого — все будет хорошо.
А эти слова разве я написал в начале статьи?
From time to time I encounter people who attempt to express an API in terms of IQueryable. That's almost always a bad idea. In this post, I'll explain why

На всякий случай напомню, что я написал почти то же самое:
весь ее посыл в том, чтобы не использовать этот интерфейс в своем API


Вам кажется. На самом деле, речь идет о замене ORM за абстрактным интерфейсом
Когда мне говорят, что мне кажется, а собеседник знает, как «на самом деле», я очень настораживаюсь: возможно у человека сверхспособности. Боюсь что в данном случае у нас с Вами просто разный прошлый опыт и набор тех проблем, с которыми мы сталкивались. Поэтому Вы смотрите на это в своем контексте, а я в своем. А кто из нас «на самом деле» прав, может сказать только Марк Симан.

Если вы предполагаете заменять хранилище — тогда зависимость от IQueryable плоха (потому что его реализации не взаимозаменяемы). Если нет — то нет.
Но заметьте, что автор ответа на SO (он же и автор вопроса) нигде не говорит о том, что собирается заменять NHibernate. И тем не менее, он зачем-то инкапсулирует IQueryable внутри репозитория.

как только мы отбираем IQueryable, мы берем на себя обязанность предоставить замену. Банальный пример: как в предложенном DSL выразить запрос на (а) начатые и незавершенные (б) начатые или незавершенные? А проекции? А джойны? Ну и понеслась. А интеграция с UI и сервисной моделью навроде OData?
А вот тут я с Вами полностью согласен. Как только у нас появляется разнообразие запросов, мы будем изобретать велосипед, создавая нечто такое же гибкое, как IQueryable, с теми же проблемами. Вопрос только в том, всегда ли нужно такое разнообразие и те возможности, о которых Вы написали.
На всякий случай напомню, что я написал почти то же самое:

Для меня важное отличие в слове «почти».

Но заметьте, что автор ответа на SO (он же и автор вопроса) нигде не говорит о том, что собирается заменять NHibernate.

Я процитировал место, где этот посыл возникает.

Вопрос только в том, всегда ли нужно такое разнообразие и те возможности, о которых Вы написали.

В моем опыте ситуации делятся ровно на две категории: те, где возникает, и те, где не возникает. Любой UI, с которым мне приходилось иметь дело, равно или поздно оказывался в первой категории.

… что, кстати, мило приводит нас к забавному паттерну, где собственно бизнес-логика и отображение в UI работают через разные слои: привет, CQRS (я знаю, что он не вполне об этом, но на его основе это делать удобнее).
Ну теперь, вроде бы, позиции наши ясны. Спасибо, lair, за дискуссию! Было интересно!
Кстати говоря, здорово было бы Вам написать пост по материалам комментариев к этой статье: Вы, по моему, большую работу проделали по разъяснению. Осталось только все это собрать вместе.
.
Как только у нас появляется разнообразие запросов, мы будем изобретать велосипед,
если запросов очень много, то лучше всего использховать habrahabr.ru/post/125720
Я работаю над проектом, в котором два боевых слоя хранилища и еще одно демонстрационное.
И сколько из них не на EF (если не считать то, которое в памяти)?
Linq2Sql и EF, 2Sql издревна пошел, проект изначально на нем писался, к стати использовалось расширение контекста как хранилище набора скомпилированных запросов, вы приводили этот прием, только работа шла на прямую с контекстом.
Ну вот и ответ, зачем вам репозиторий поверх EF — чтобы иметь возможность не использовать EF. Good luck with that.
у нас было несколько экспериментов по EF, разные комбинации, если использовать провайдеры от разного производителя, то результат не очень, с одним производителем дела на много лучше, но проблемы с запросами есть.
В моей практике ORM скрывался за репозиторием когда источником данных выступала как реляционная бд, так и Key-Value хранилище. И подмена эта происходила в рантайме в зависимости от неких параметров.
А каким был интерфейс репозитория? Универсальным, т.е. выставлялся IQueryable или его аналог, или просто содержал конечный набор методов, который и определял набор возможных запросов?

И еще интересно было бы узнать Ваше мнение. Допустим в некой задаче нет необходимости поддерживать разные хранилища/ORM, есть только EF и, к примеру, SQL Server. Но в то же время набор возможных запросов к БД сильно ограничен и расширяется очень предсказуемо. Стали бы Вы оборачивать EF в репозиторий с простым интерфейсом, или же просто использовали бы DbContext?
Допустим в некой задаче нет необходимости поддерживать разные хранилища/ORM, есть только EF и, к примеру, SQL Server. Но в то же время набор возможных запросов к БД сильно ограничен и расширяется очень предсказуемо. Стали бы Вы оборачивать EF в репозиторий с простым интерфейсом, или же просто использовали бы DbContext?

Я в описанной ситуации — только доведенной до экстремума, там все взаимодействие с БД сводилось к 5-8 методам — построил отдельный DAL-интерфейс, из банальной мотивации: его было намного легче подменять в моке. Но это именно потому, что операций было очень мало.
А каким был интерфейс репозитория? Универсальным, т.е. выставлялся IQueryable или его аналог, или просто содержал конечный набор методов, который и определял набор возможных запросов?

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

И еще интересно было бы узнать Ваше мнение. Допустим в некой задаче нет необходимости поддерживать разные хранилища/ORM, есть только EF и, к примеру, SQL Server. Но в то же время набор возможных запросов к БД сильно ограничен и расширяется очень предсказуемо. Стали бы Вы оборачивать EF в репозиторий с простым интерфейсом, или же просто использовали бы DbContext?

Я не вижу смысла городить тут репозиторий, хотя, возможно, выставил бы интерфейс с ограниченным набором методов вместо Generic интерфейса с Set (этот ограниченный набор методов реализовал бы прямо на DbContext)
Но, такие интерфейсы очень быстро разрастаются до нескольких десятков методов и тут важно быстро это прекратить и свести к Set.
GetFooByParamSet2(paramSet2);
GetFooByParamSet3(paramSet3);
//--------
GetFooByParamSetN(paramSetN);

а что, не плохо. мы только что ограничили запросы, а вот теперь нам нужно получить элементы по paramSet2 и paramSet3, и вот
GetFooByParamSetN+1(paramSetN+1);
100% рабочий вариант, спору нет, но чем он лучше? проще — да, но попытка комбинирования запросов приводит к разрастанию интерфейса — это нормально?
Вопрос не ко мне, а к SVVer. Я просто выставляю IQueryable<T> Set<T> в самом общем случае, или IQueryable<A> A когда хочу больше контроля.
Вы имеете ввиду, что набор возможных запросов к репозиторию со временем разрастается так, что лучше иметь IQueryable или его аналог?
И как же репозиторий позволяет обойти эти проблемы? Вероятно, вы имеете в виду, что вы напишете две разных реализации репозитория для двух разных провайдеров. Это здорово, но вы помните, что вы только что увеличили свои затраты вдвое?

В двое? Я реализую абстракцию, которая позволит переопределить обработку запросов отдельно, и в каждой реализации переопределю только не эффективные.
Как же репозиторий ее решает в таком случае?

написано причем не мной
Однако это не всегда возможно.

на это я ответил выше.
В двое? Я реализую абстракцию, которая позволит переопределить обработку запросов отдельно, и в каждой реализации переопределю только не эффективные.

А где реализованы те, которые вы переопределяете.

Как же репозиторий ее решает в таком случае?

написано причем не мной

Так как же? Написанное всегда можно процитировать.

Однако это не всегда возможно.

на это я ответил выше.

Можете повторить? Я не очень понимаю ни на что вы ответили, ни что вы ответили.
А где реализованы те, которые вы переопределяете.

в базовом репозитории.
Во-первых, вы передергиваете: нигде не сказано, что эта задача не имеет решения; сказано лишь что не всегда возможно сгенерировать корректный SQL для всех БД для любого AST. Но предположим на минуту, что вы правы, и эта задача действительно не имеет решения. Как же репозиторий ее решает в таком случае?

Задача не имеет решения. я это комментирую
написано причем не мной
Однако это не всегда возможно.

на
и эта задача действительно не имеет решения. Как же репозиторий ее решает в таком случае?

на это ответ выше, можно выполнить другой эквивалентный запрос внутри репозитория.
А где реализованы те, которые вы переопределяете.

в базовом репозитории.

Наверное, вами же реализованы? Вот и увеличение ресурсных затрат.

можно выполнить другой эквивалентный запрос внутри репозитория

Ох. Давайте пойдем сначала. Вот есть проблема: не всякий запрос, переданный в DAL снаружи (и, как следствие, сформулированный в терминах объектов), можно преобразовать в запрос в терминах хранилища. Я правильно вас понял, или вы что-то другое имеете в виду?
Наверное, вами же реализованы? Вот и увеличение ресурсных затрат.

хорошую архитектуру бесплатно не получить.
Ох. Давайте пойдем сначала. Вот есть проблема: не всякий запрос, переданный в DAL снаружи (и, как следствие, сформулированный в терминах объектов), можно преобразовать в запрос в терминах хранилища. Я правильно вас понял, или вы что-то другое имеете в виду?

все что можно написать в LINQ запросах не всегда можно отразить в SQL запросе.
хорошую архитектуру бесплатно не получить.

Конечно. За то, что вы называете хорошей архитектурой, вы чем-то платите. Возможно, для всех остальных эти расходы не являются настолько оправданными по сравнению с предлагаемым выигрышем. Из вашей статьи создается ложное ощущение, что преимущества Repository бесплатны. Это не так.

все что можно написать в LINQ запросах не всегда можно отразить в SQL запросе.

И как репозиторий решает эту проблему?
преимущества Repository бесплатны
я ни где об этом не пишу, я пишу что это способ решить проблему совместимости
И как репозиторий решает эту проблему?

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

Вы пишете, что все описанные проблемы можно легко решить с помощью repository.

передав информацию о том что нужно, репозиторий переведет ее в как получить.

А разве (любой LINQ-based DAL) делает не то же самое? В чем преимущества репозитория в решении этой проблемы?
Вы пишете, что все описанные проблемы можно легко решить с помощью repository.

легко не не бесплатно.
А разве (любой LINQ-based DAL) делает не то же самое? В чем преимущества репозитория в решении этой проблемы?
то же самое, но один и тот же LINQ запрос может по разному обрабатываться разными провайдерами, а именно построенный запрос для конкретной базы мб не эффективным или даже покрашить систему.
В репозитории можно это все компенсировать.
Как именно можно все это компенсировать в репозитории?
по разному формировать запросы, которые формируются в конкретном репозитории
То есть запросы формирует репозиторий, а не код-потребитель? Тогда вы не решаете проблему, вы ее избегаете. Теперь давайте выясним, за счет чего: как же именно код-потребитель объясняет репозиторию, какие объекты ему нужны?
объект запроса в котором описано что нужно получить. я там где то обещал пример…
Вот и увеличение ресурсных затрат.


Вероятно имеется в виду, что при необходимости поддерживать эффективно две SQL-базы данных можно вынести общие и(или) некритичные части в родителя (или ещё как сделать совместно используемыми), пользоваться дефолтными реализациями, а затачивать под конкретную СУБД только ту часть запросов, что критична, причём именно в данном приложении. При такой организации оценка «в 2 раза больше ресурсов» будет пессимистичной, если каждый запрос будет не будет критичным, подлежащим оптимизации под конкретную СУБД.
Да я понимаю, что имеется в виду, только вот к чему это в реальности приводит:

  • нам нужен общий интерфейс для провайдеров к разным БД
  • этот интерфейс мы запихиваем в базовый класс репозитория
  • нам нужны провайдеры, удовлетворяющие этому интерфейсу
  • поверх этого интерфейса мы пишем расширения в каждой реализации репозитория для конкретного провайдера БД
  • в написании этих расширений мы ограничены возможностями провайдеров из поза-предыдущего пункта


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

Но это еще полбеды. Вот вам вторая занимательная половина: репозиторий обычно специфичен для сущности (или агрегата, если у нас DDD). Иными словами, код-потребитель использует не repository.Get<Product>, а productRepository.Get. И это даже имеет смысл, потому что для каждой конкретной сущности наиболее эффективный способ получения/фильтрации/отображения может отличаться. И обычно эти классы все наследуются от базового репозитория, чтобы переиспользовать базовые операции. И вот тут-то и возникает занимательный конфликт (ну, занимательный он для языков без множественного наследования) — у нас есть общий код для (а) всех репозиториев (б) репозиториев под MS SQL (в) репозиториев продуктов — как бы нам теперь весь его переиспользовать в конкретном репозитории продуктов в MS SQL?
нам нужен общий интерфейс для провайдеров к разным БД
да
этот интерфейс мы запихиваем в базовый класс репозитория
да
нам нужны провайдеры, удовлетворяющие этому интерфейсу
не понял юмора, зачем провайдер?
поверх этого интерфейса мы пишем расширения в каждой реализации репозитория для конкретного провайдера БД
только необходимые
в написании этих расширений мы ограничены возможностями провайдеров из поза-предыдущего пункта
только из за не совместимости провайдеров это нужно
как бы нам теперь весь его переиспользовать в конкретном репозитории продуктов в MS SQL?

единственное что нужно будет модифицировать это некоторые преобразователи запроса в завпросы к хранилищу (для EF это будут специфичные LINQ запросы)
Иэх. Похоже, без примеров кода не обойтись. Давайте начнем с простого: вот у вас есть репозиторий чего-нибудь, нам надо сделать в нем «получить постраничный список» (на входе — номер страницы и размер страницы), как вы это будете реализовывать?
если в основе репозитория лежит реализация использующая EF, то на IQuariable Order/Skip/Take.
Можете привести короткий пример кода?
Только из за интереса.
set.DefaultOrder().Skip(skipCount).Take(takeCount).Map()
можно и такой записью
Map(DefaultOrder(set).Skip(skipCount).Take(takeCount))
правда вторая запись хуже читаемая
Очень хорошо. А теперь давайте возьмем провайдер БД A и провайдер БД B, из которых один поддерживает Skip/Take, а второй — нет. Как паттерн Repository поможет вам решить эту проблему?

PS Обратите внимание, что абстракция skipCount/takeCount отличается от постраничного списка, описанного мной выше. Это для данного примера не очень критично, но вообще показательно.
Очень просто, я написал общий вариант, оборачивается все в функции типа Shift и TakeEntity
что то типа того
set.DefaultOrder().Shift (skipCount).TakeEntity(takeCount).Map()
и там где нужно можно переопределить DefaultOrder Shift TakeEntity
правда мы используем построитель запросов.
Где именно реализуются методы Shift и TakeEntity? Можете привести пример кода одного (любого) из них?
Самый простой и самый не хороший метод это функции самого репозитория
virtual IQuariable<T> Shift (IQuariable<T> set, long conut)
{
   return set.Skip(count);
}

если реализация по каким то причинам не подходит, ее можно переопределить.
правда мы используем построитель запросов.
Правильно ли я понимаю, что вы предлагаете завести три репозитория (типизация по сущности опущена специально)?

class GenericRepository
{
  public IEnumerable GetPaged(int skip, int take)
  {
     return set.DefaultOrder().Shift(skip).TakeEntity(take).Map();
  }

  protected virtual IQueryable Shift(IQueryable source, long count)
  {
     return source.Skip(count);
  }
}

class DbProviderARepository: GenericRepository
{
 //не оверрайдим ничего, все работает "из коробки
}

class DbProviderBRepository: GenericRepository
{
  protected virtual IQueryable Shift(IQueryable source, long count)
  {
     //например, так, детали не очень важны
     return source.AsEnumerable().Skip(count).AsQueryable();
  }
}
Нет!
public IEnumerable GetPaged(int skip, int take)

это будет работать нормально только если запросов очень ограниченное количество, я не на столько болен что бы такое предлагать, хотя…
 public IEnumerable Get(Query)

это выставляется во вне, все остальное скрыто за фасадом, причем запросы для каждого репозитория свои, например всегда ли нужен постраничный вывод?
Вот именно поэтому я и просил вас привести код, чтобы не играть тут в угадайку.

Пожалуйста, покажите, как в вашем случае реализуется описанный мной сценарий (необходимость постраничного вывода, две СУБД, одна поддерживает Skip, другая — нет). Меня интересует структура задействованных классов и конкретные точки расширения, которые вы используете, чтобы обойти проблему.
1. Формируется запрос в терминах предметной области
var query = new Query();
query.PagingOptions.SkipCount = 10;
query.PagingOptions.TakeCount = 10;
repository.Get(query);

2. Базовый Rep обрабатывает этот запрос
public virtual IEnumerable<Entity> Get( Query query )
{
	IQueryable<Entity> filtrate = _table;
	//обработка запроса
        //-----------
        ...

        filtrate = Build( filtrate, query.PagingOptions );
	return Map(filtrate);
}

3. Магический метод построения запроса
private IQueryable<Entity> Build<Q>( IQueryable<Entity> filtrate, Q query ) where Q : struct
{
	if ( !_builders.ContainsKey( typeof( Q ) ) )
		return filtrate;

	var builder = (IQueryBuilder<Entity, Q>)_builders[typeof( Q )];
	return builder.Build( filtrate, query );
}

4. Конкретный строитель запроса
public class PagingQueryBuilder<T> : IQueryBuilder<T, PagingOptions>
{
	public IQueryable<T> Build( IQueryable<T> queriable, PagingOptions query )
	{
		var buildQuery = queriable;
		if ( query.SkipCount > 0 )
		buildQuery = buildQuery.Skip( query.SkipCount );
		if ( query.TakeCount > 0 )
			buildQuery = buildQuery.Take(query.TakeCount);
		return buildQuery;
	}
}

он является универсальным, можно заморочиться и распилить на 2 (но я не уверен что это нужно, хотя я могу ошибаться)
5. Конфигурация строителей
 protected Dictionary<Type, object> _builders;

в базовом репозитории для типа сущности идет общая конфигурация, конкретная реализация может управлять регистрацией и чем меньши блоки запросов, тем гибче ( минус только в том что таких запросов может быть много )
Вы писали про 18 критериев фильтрации (правда походу не в этой теме, но отвечу тут, подходящие место), вам всеравно нужно будет их анализировать при построение Linq запроса, так почему же их не сгрупировать на критерии подзапросов, оформить в запрос и передать репозиторию. Или вы предпочитаете эти 18 параметров засунуть в 1 LINQ запрос? или еще хуже передать эти 18 параметров в функцию?
Так в каком же конкретно месте происходит выбор специфичного для конкретной БД способа пейджинга? Если в QueryBuilder, то, удивительным образом, репозиторий для этого не нужен.

Вы писали про 18 критериев фильтрации (правда походу не в этой теме, но отвечу тут, подходящие место), вам всеравно нужно будет их анализировать при построение Linq запроса, так почему же их не сгрупировать на критерии подзапросов, оформить в запрос и передать репозиторию. Или вы предпочитаете эти 18 параметров засунуть в 1 LINQ запрос? или еще хуже передать эти 18 параметров в функцию?

Я предпочитаю взять 18 критериев фильтрации, пришедших с клиента в виде одного AST, сконвертировать их в другой AST и скормить в DAL. Просто и прямолинейно.
Так в каком же конкретно месте происходит выбор специфичного для конкретной БД способа пейджинга? Если в QueryBuilder, то, удивительным образом, репозиторий для этого не нужен.
Нужен, конфигурация билдера происходит в нем, да это можно сделать и в других местах, репозиторий — фасад, скрывающий создание/удаление/запросы/маппинг и конкретную технологию доступа к данным. Я считаю что очень важно запрещать прямой доступ к контексту данных, тк мы получим описанные здесь проблемы.
конфигурация билдера

Что именно вы под этим подразумеваете?

Я считаю что очень важно запрещать прямой доступ к контексту данных

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

Чем больше вы прячете, тем меньше возможностей вы оставляете пользовательскому коду, тем беднее он становится (если, конечно, вы не тратите неимоверные ресуры на написание полного DSL).
Что именно вы под этим подразумеваете?
связь реализации построителя с параметром запроса.
Для этого достаточно — всего лишь — убрать контекст за интерфейс. Не создать новую прослойку, а просто выделить интерфейс с нужными методами.
сделаем Create Save Delete Query и получим тот же репозиторий.
Чем больше вы прячете, тем меньше возможностей вы оставляете пользовательскому коду, тем беднее он становится (если, конечно, вы не тратите неимоверные ресуры на написание полного DSL).
Меньше возможностей пользовательскому коду — меньше ошибок, да это может доставить некоторые неудобства, но не зря появляется куча DSL, например XAML.
сделаем Create Save Delete Query и получим тот же репозиторий.

Не, не получите — потому что у него будет другая семантика. EF за интерфейсом — все тот же EF.

Меньше возможностей пользовательскому коду — меньше ошибок

Ну, если вы считаете себя заведомо умнее разработчика, который пользуется вашим кодом, то тут мне помочь нечем.
Я предпочитаю взять 18 критериев фильтрации, пришедших с клиента в виде одного AST, сконвертировать их в другой AST и скормить в DAL. Просто и прямолинейно.
да все этого хотят, но на практике приходиться плясать, и не только с бубном, что бы желания вписывались в реалии.
Видимо, у нас с вами разная практика.
Видимо, но зато спор вышел увлекательный.
Или вы предпочитаете эти 18 параметров засунуть в 1 LINQ запрос? или еще хуже передать эти 18 параметров в функцию?

У linq запросов есть потрясающее свойство — композируемость. Тебе не надо 18 параметров передавать в функции. Ты можешь 18 раз применить функции (комбинаторы), при этом каждая функция будет тривиальной (IQueryable, param) => IQueryable.

Если же тебе надо будет в каждом вызове передавать 18 параметров, да еще и править каждый раз код при добавлении нового, то ты быстро забьешь на это дело и будешь просто тянуть всю простыню данных на клиент, а потом фильтровать уже объекты. И под любой серьезной нагрузкой это ляжет.
У linq запросов есть потрясающее свойство — композируемость. Тебе не надо 18 параметров передавать в функции. Ты можешь 18 раз применить функции (комбинаторы), при этом каждая функция будет тривиальной (IQueryable, param) => IQueryable.
именно по этой схеме и работает QueryBuilder о котором я пишу, и нам все равно нужно анализировать каждый из параметров отдельно.
Если же тебе надо будет в каждом вызове передавать 18 параметров, да еще и править каждый раз код при добавлении нового, то ты быстро забьешь на это дело и будешь просто тянуть всю простыню данных на клиент, а потом фильтровать уже объекты. И под любой серьезной нагрузкой это ляжет.
кто ж спорит.
именно по этой схеме и работает QueryBuilder о котором я пишу, и нам все равно нужно анализировать каждый из параметров отдельно.

Вам — возможно. А во всех адекватных реализациях LINQ-провайдеров для этого написана обобщенная функция.
я про построение запроса, такие условия как больше, меньше, равно, есть параметр нет… это все равно где то придется анализировать, и очень будет плохо, если подобных запросов по коду будет много и что то придется менять.
В описанном выше сценарии (про 18 параметров) они все прилетают из UI, они не могут измениться. А бизнес-правила (навроде «активные заказы») прекрасно выпинываются в list comprehensions. Правда, если нужна их композиция по «или», то ситуация сложнее, но и тут можно сделать нормальный AST builder.
именно по этой схеме и работает QueryBuilder о котором я пишу, и нам все равно нужно анализировать каждый из параметров отдельно.


При изменении набора параметров надо лезть и править query builder? Такое решение нежизнеспособно даже при небольшом количестве параметров.
public virtual IEnumerable<Entity> Get( Query query )
{
	IQueryable<Entity> filtrate = _table;
	//обработка запроса
        //-----------
        ...

        filtrate = Build( filtrate, query.PagingOptions );
	return Map(filtrate); //Ахтунг
}

Прочитал ваш код, у вас нет проекций, вы всегда тянете полные сущности. Это тормозит.

Но это еще пол-беды. Самое интересное, что метод Get у вас принимает один Query, то есть никакой декомпозиции, все параметры запроса надо выписать по месту вызова. И разные Query вы не сделаете, надо будет много копипастить. У каждой сущности будет постраничная разбивка, у половины фильтры активных, еще у части фильтры доступа.

То есть у вас в Query должны быть все возможные параметры для всех возможных запросов. На практике нежизнеспособное решение.

Из-за этого код Build непонятно зачем нужен, у вас же всегда один тип QueryBuilder будет, который фактически разбирает Query со всеми параметрами.

Кстати вы применил зачем-то словарь, вместо полиморфизма. Почему Query не может накладывать фильтры на IQueryable сам?

И в итоге все это делалось, чтобы вызвать разные методы Build для разных провайдеров. Только я так и не понял, почему нельзя нужный QueryBuilder заинжектить в БЛ и не использовать репозитории вообще? И проблем с проекциями не будет.
Прочитал ваш код, у вас нет проекций, вы всегда тянете полные сущности. Это тормозит.
это простейший пример, для оптимизации используются параметры прогрузки сущностный (аля Include ), так же в терминах предметной области.
Самое интересное, что метод Get у вас принимает один Query, то есть никакой декомпозиции, все параметры запроса надо выписать по месту вызова.
декомпозиция — если не заполнено, значит нет,
У каждой сущности будет постраничная разбивка, у половины фильтры активных, еще у части фильтры доступа.
плохо написано, возможно не разобрал, но для каждого репозитория свой query
То есть у вас в Query должны быть все возможные параметры для всех возможных запросов. На практике нежизнеспособное решение.
правильно определив корни агрегации и хорошо зная предметную область разбив все крупные запросы на простые как правило получается что и вариантов та таких подзапросов на конкретную предметку не много.
Кстати вы применил зачем-то словарь, вместо полиморфизма. Почему Query не может накладывать фильтры на IQueryable сам?
для того что бы решить проблему совместимости, переопределив нужные билдеры запросов
И в итоге все это делалось, чтобы вызвать разные методы Build для разных провайдеров. Только я так и не понял, почему нельзя нужный QueryBuilder заинжектить в БЛ и не использовать репозитории вообще? И проблем с проекциями не будет.
потому что реп это не просто построитель запросов — он умеет создавать сохранять удалять еще и маппинг,
это простейший пример, для оптимизации используются параметры прогрузки сущностный (аля Include ), так же в терминах предметной области.

Это еще хуже. Чтобы работало с приемлемой скоростью нужны проекции. Иначе простейший список документов с фильтром по автору будет тормозить уже на паре тысяч записей.

декомпозиция — если не заполнено, значит нет

Это не декомпозиция, плохая шутка.

для каждого репозитория свой query

Я про это и говорю. У каждого Query будут возможности постраничной разбивки, фильтры для разграничений доступа итп. То есть будет много копипасты. При этом разные сущности могут поддерживать разные возможности. Например у некоторых есть флаги неактивности, а у других нет. У некоторых есть фильтры с учетом ролей пользователя, а у других нет итд. Причем копипаста в итоге будет как в самом классе query, так и в QueryBuilder.

как правило получается что и вариантов та таких подзапросов на конкретную предметку не много.

Вариантов запросов может быть сколько угодно. Можно вообще всегда один запрос иметь и вытягивать таблицу целиком. Но чтобы работало быстро — надо иметь много запросов, под каждый сценарий нужен свой специализированный запрос. У меня примитивное приложение с 5 таблицами генерирует 150 разных запросов. Там и проекции, и джоины, и фильтры. А если добавить разграничение доступа и нетривиальную логику, то запросов станет сотни три.

правильно определив корни агрегации и хорошо зная предметную область

Берем простой интернет-магазин. Товары-Клиенты-Заказы-Позиции. 4 сущности всего.
Функции: каталог товаров, создание заказа, список заказов для менеджера, статистика продаж товаров, список заказов клиента (для расчета скидки в онлайн).
Что будет корнями агрегации?

потому что реп это не просто построитель запросов — он умеет создавать сохранять удалять еще и маппинг
То же самое делает EF. То есть вы в репозитории объединили функции построения запросов и исполнения запросов, что нарушает SRP, да еще и пессимизироали программу.
Где же польза?
Причем копипаста в итоге будет как в самом классе query, так и в QueryBuilder.
о каком копипасте идет речь в билдере?
Но чтобы работало быстро — надо иметь много запросов, под каждый сценарий нужен свой специализированный запрос.
поэтому и описние идет в терминах предметной области, а там где запрос выполняется идет анализ как лучше обработать данный запрос.
У меня примитивное приложение с 5 таблицами генерирует 150 разных запросов.
на сколько элементарных подзапросов можно их разбить? или они все уникальны?
Берем простой интернет-магазин. Товары-Клиенты-Заказы-Позиции. 4 сущности всего.
Функции: каталог товаров, создание заказа, список заказов для менеджера,
а для магеров таблички нет? все сущности являются корнями..
То же самое делает EF. То есть вы в репозитории объединили функции построения запросов и исполнения запросов,
умеет ли он строить идентичные запросы которые будут одинаково адекватно выполняться на разных БД — нет. Безопасно ли заменять один провайдер другим — нет. Умеет ли EF адекватно сохранять корни агрегации — нет, умеет ли он правильно сохранять корни — нет.
что нарушает SRP
Secure Remote Password?
да еще и пессимизироали программу
пессимизироали это противоположенное оптимизировали?
о каком копипасте идет речь в билдере?

У вас свой Query на каждый репозиторий, так? И почти в каждом Query есть paging, так? И для каждого Query есть свой Bulder. И в каждом builder есть обработка paging. Не копипаста?

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

Лучше — сделать проекцию, но вы не можете так сделать, потому что хотите тянуть целые объекты. Все остальное быстродействию не поможет. Самый лучший запрос можно составить точно зная как его результаты будут обрабатываться. Увы ваш репозиторий лишает такой возможности.

на сколько элементарных подзапросов можно их разбить? или они все уникальны?

Каких «элементарных подзапросов»? С точки зрения SQL Server все запросы уникальны. У меня в коде от силы 10 местах происходит обращение к IQueryable. А дальше эти обращения комбинируются в итоговый запрос и накладывается проекция. На выходе получается порядка 150 разных запросов.

а для магеров таблички нет? все сущности являются корнями..

То есть запросов получится много даже для такой примитивнй модели. Что уж говорить о сложных моделях…

умеет ли он строить идентичные запросы которые будут одинаково адекватно выполняться на разных БД — нет.

EF запросы не строит, их строит программист.

Безопасно ли заменять один провайдер другим — нет.

Прости, но никто не умеет. Если у тебя в модели DateTimeOffset, то половина провайдеров тебя пошлют нафиг. Если у тебя нет DateTimeOffset в модели, то все провайдеры для EF спокойно прожуют твою модель. Лишние прослойки тут не помогут.

Умеет ли EF адекватно сохранять корни агрегации — нет, умеет ли он правильно сохранять корни — нет.

Мы выше рассмотрели простой пример модели, где каждая сущность является корнем и EF с ними нормально работает. Это при том, что в 90% статей про aggregation root ты увидишь пример агрегата Заказ-ПозицияЗаказа ;)
Есть подозрение что агрегаты существуют только в таких статьях.

что нарушает SRP

Secure Remote Password?

Single Responsibility Principle

пессимизироали это противоположенное оптимизировали?

Да, сделали заведомо медленнее, чем она могла бы быть.
У вас свой Query на каждый репозиторий, так?
Build(filtrate,query.Paging) — много копи паста.
Лучше — сделать проекцию, но вы не можете так сделать, потому что хотите тянуть целые объекты. Все остальное быстродействию не поможет. Самый лучший запрос можно составить точно зная как его результаты будут обрабатываться. Увы ваш репозиторий лишает такой возможности.
я привел самый простой случай, ни кто не мешает передать в билдер context и там вывернуть его на изнанку.
То есть запросов получится много даже для такой примитивнй модели. Что уж говорить о сложных моделях…
да ладно, можно группировать запросы, например запрос зака, передовать с запросом пользователя в репозитория пользователей, обернуть билдеры в фабрику, а в нутри репозитория можно это все дело совместить и скомбинировать, сделать проекции и тд, не проблема
EF запросы не строит, их строит программист
вот именно, это на его совести, к сожалению совесть вещь не предсказуемая.
Прости, но никто не умеет. Если у тебя в модели DateTimeOffset, то половина провайдеров тебя пошлют нафиг. Если у тебя нет DateTimeOffset в модели, то все провайдеры для EF спокойно прожуют твою модель. Лишние прослойки тут не помогут.
речь не только о типах, но и о самих провайдерах, как они отработают на одних и тех же запросах заранее сказат ьне возможно
Build(filtrate,query.Paging) — много копи паста.

Не у каждого query будет Paging.

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

да ладно, можно группировать запросы, например запрос зака, передовать с запросом пользователя в репозитория пользователей, обернуть билдеры в фабрику, а в нутри репозитория можно это все дело совместить и скомбинировать, сделать проекции и тд, не проблема

Тогда репозиторий превратится в бизнес-логику.

вот именно, это на его совести, к сожалению совесть вещь не предсказуемая.

Весь код на совести программиста. Думаешь у программистов, которые пишут репозитории другая совесть?

речь не только о типах, но и о самих провайдерах, как они отработают на одних и тех же запросах заранее сказат ьне возможно

Вот именно, поэтому совершенно неясно как поможет репозиторий. Все равно надо будет написать-протестировать-поправить. Репозиторий для этого категорически не нужен.
Не у каждого query будет Paging.
вот именно! каждый из репозиториев будет принимать свой тип запроса.
случай работает сильно медленнее
а вы можите написать выражение которое будет делать скип и тэйк быстрее?
Тогда репозиторий превратится в бизнес-логику.
то есть если query является составным query.UserOptions и query.OrderOptions то обработка этого запроса это бизнес уровень, а написать LINQ запрос который будет получать пользователей по определенному критерию заказов — это нет. А в чем разница?
Думаешь у программистов, которые пишут репозитории другая совесть?
таже, но ограничив запросы предметной области идет естественное ограничение совести, тк нет универсального построителя в котором безсовестно можно писать все что угодно.
вот именно! каждый из репозиториев будет принимать свой тип запроса.

То есть Build(filtrate,query.Paging) надо будет скопипастить как минимум в половине билдеров. Это и есть копипаста.

а вы можите написать выражение которое будет делать скип и тэйк быстрее?
Конечно, нужно сделать проекцию (select).

А в чем разница?

Разница в том, что бизнес-логика, то есть та логика, которая непосредственно относится к решению бизнес-задачи очень часто подвергается изменениям. А твой подход рождает изрядное количество копипасты. БЛ с таким подходом писать нельзя в принципе.

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

А кто помешает программисту написать любой запрос внутри репозитария? Это вообще глупая идея, что программиста можно в чем-то ограничить. Ограничить может компилятор или тест, но тест тоже пишет программист, поэтому кроме компилятора рассчитывать не на что.
Build(filtrate,query.Paging) надо будет скопипастить как минимум в половине билдеров.
тоже самое что и рассматривать за копипаст Skip(n).Take(k), для особого извращения это все можно сделать с помощью рефлексии ( крайне не рекомендую ) универсально.
А твой подход рождает изрядное количество копипасты.
запросы изменяются для каждого репозитория отдельно, причем эти запросы специфический, такие запросф как Paging с точки зрения предметной области меняться не будут, может меняться только их построение.
А кто помешает программисту написать любой запрос внутри репозитария?
ни кто, но так он будет находиться в конкретном месте.
кроме компилятора рассчитывать не на что.
к сожалению компилятор не в силах распознать LINQ запросы которые приведут к краху, тк с точки зрения статического анализа они верны, но перевести их в SQL imposible.
тоже самое что и рассматривать за копипаст Skip(n).Take(k)

Не то же самое. Считаем для пейджинга со Skip(n).Take(k) нужно написать только эти вызовы и больше ничего.
Для твоего варианта с Query надо:
1) Написать код объекта Query со свойством Paging
2) Написать инициализацию ствойства
3) Создать объект в месте вызова
4) Заполнить свойство Paging
5) Написать билдер

Все кроме п4 — водопровдный код, а по твоей логике он будет копипаститься для каждого класса Query. И самое смешное что 99,99% случаев он сведется к тем же Skip\Take.

А кто помешает программисту написать любой запрос внутри репозитария?

ни кто, но так он будет находиться в конкретном месте.

Не будет. У тебя формирование запроса будет размазано по десятку билдеров, каждый еще и со своей нетривиальной логикой. Кто помешает там написать плохой запрос?
5) Написать билдер
это делается 1 раз,
Кто помешает там написать плохой запрос?
ни кто, но это будет не в 10 местах по коду, а в одном, и так же легко поправиться
это делается 1 раз,

Один раз на каждый тип запросов.

ни кто, но это будет не в 10 местах по коду, а в одном, и так же легко поправиться

Ты сам выше сказал, что у тебя будут методы Build, которые обрабатывают свои части запросов, потому что формирование запроса уже размазано по куче методов Build.
смысл в дефрагментации крупного запроса на простые.
Правильно, поэтому у тебя никогда не будет весь запрос в одном месте и контролировать запросы будет сложно. Поэтому и нет смысла запросы прятать в репозитарии, кроме снижения быстродействия это ничего не даст.
при появлении новых запросов строиться новый билдер только для него, тестируется как запрос отрабатывает на разных репах, если на кком то что то плохо, то правки только для этой реализации. удобно кстате
Верю, только для этого репозитарий вовсе не нужен. Ранее я приводил пример кода, который точно также подменяется и тестируется при необходмости.
для оптимизации используются параметры прогрузки сущностный (аля Include ), так же в терминах предметной области.

Я и говорю — по факту, вы написали свой аналог LINQ, только попроще. Вы вынуждены были повторить половину имеющихся там фич.
нет не аналог, при работе с LINQ вы не множите управлять построением запросов при смене провайдера
А что мне мешает-то? Вообще-то, поправить AST на лету — не запредельной сложности задача.
context.Where(e=>e.Value == true). простой пример, я в коде на прямую обратился к контексту, сменил провайдер и запрос неэффективен, как мне выйти из этой ситуации с гарантией, что никто никогда это не сможет повторить.
Скрыть запрос за интерфейсом, который будет отдавать ваш собственный IQueryable.
то есть это IQuariable не из Context?
Если у меня есть потребность контролировать запросы, передаваемые внешним кодом, то да.
и вы будите анализировать epression tree и на его осннове формировать новый AST (как вы любите писать)?
Во-первых, да.
Во-вторых, я могу врезаться еще в этап построения дерева, это в большинстве случаев упростит мне анализ.
Ух, это жесть. Плохо, что она не лишена обсуждаемой сдесь проблемы, вам все равно нужно будет любой запрос перевести в дерево и передать его провайдеру, слабое звено в пищевой цепочки скармливания деревьев — провайдер, он просто может не переварить приготовленную кухню.
У вас под вашим репозиторием тот же самый провайдер, так что это слабоватый аргумент, вы не находите?

(Я бы понял, если бы речь шла про EF vs Repository over any-other-DAL)
Тот же самый, но меньше возможностей у клиента покрошить выполнение, тк он ограничен запросом синтаксически.
Что возвращает нас к уже описанному выше тезису: вы даете потребителю вашего кода ограниченное (причем сильно) подмножество возможностей. Это та цена, которую ваш потребитель платит за ваше решение использовать репозиторий.

Кстати, а что вы будете делать в следующем сценарии: вы дали потребителю некий запрос, а потом вам понадобилось добавить поддержку еще одной БД. А в ней, вот незадача, этот запрос невыполним. Ваши действия?
Ответ лежит в совместимости конкретных провайдеров. Вот что о совместимости говорит один из разработчиков таких провайдеров Yevhen Shchyholyev www.infoq.com/articles/multiple-databases.

Кстати, вы на дату смотрели?
и что? Проблема лежит в попытке универсализации доступа к совершенно разным системам, что на практике не получается.
Проблема лежит в попытке универсализации доступа к совершенно разным системам, что на практике не получается.

Бинго. У EF не получается, но, по вашим утверждениям, получается у паттерна репозиторий. Как?
Каждый репозиторий знает для чего он нужен, а значит ему не нужна универсализация, он работает с тем о чем знает вся логика по обработке запросов находиться именно в нем.
Так каждый конкретный db-провайдер тоже знает, для чего он нужен, далее все аналогично. Видимо, разница не в этом.
В том что разные провайдеры по разному переводят одни и те же LINQ запросы, я об этом, и это надо учитывать.
А разные репозитории по-разному переводят одни и те же запросы предметной области — это не надо учитывать?
Для того и делается репозиторий что бы это учесть и там где это действительно нужно иметь возможность переопределить.
Какая разница потребляющему коду, кто по разному трактует его запросы — репозиторий или LINQ-провайдер? Результат-то один и тот же: один и тот же с точки зрения клиента код может иметь неожиданно разное поведение на разных БД.
код может иметь неожиданно разное поведение на разных БД.

если один и тот же LINQ на одном провайдере выполняется нормально, а на другом крашит приложение, то нам нужно переписать запрос под второй провайдер и только под него.
Вы будете «переписывать» AS-дерево, переданное пользователем, или же просто не позволите ему использовать AS-деревья?
я выше код привел, того что делаем мы
Прочитал статью дважды очень внимательно, но не понял зачем нужен Repository/

Типы данных

Логично что надо использовать только то подмножество типов, которое понимается всеми базами. Иначе затраты на мапинг превысят все разумные пределы.

Поддержка DateTimeOffset

См выше, про поддерживаемое подмножество.

Производительность

Ограничения запроса

Решается тем, что делаются разные генераторы запросов под разные базы, то есть разные наборы функций DbContext => IQueryable. Причем не для всех запросов, а только для тех где имеются значимые различия (обычно не более 5%).

В итоге я так и не понял зачем городить Repository. И это как-бы самый сложны случай, когда надо поддерживать несколько СУБД. По факту же 99% и более приложений работают с одной базой и у них вообще таких проблем нет.
Решается тем, что делаются разные генераторы запросов под разные базы, то есть разные наборы функций DbContext => IQueryable. Причем не для всех запросов, а только для тех где имеются значимые различия (обычно не более 5%).
как мы в коде будем определять какие можно запросы использовать а какие нет?
В итоге я так и не понял зачем городить Repository. И это как-бы самый сложны случай, когда надо поддерживать несколько СУБД. По факту же 99% и более приложений работают с одной базой и у них вообще таких проблем нет.
так и есть, но если бы вы действительно внимательно читали статью
Я работаю над проектом, в котором два боевых слоя хранилища и еще одно демонстрационное.
как мы в коде будем определять какие можно запросы использовать а какие нет?


А зачем это определять?
А зачем это определять?

как зачем? если система работает с несколькими провайдерами, и мы выяснили что нет возможности написать общий LINQ запрос что бы он был приемлемый для этих провайдеров.
И? Зачем определять?

Чтобы узнать что запрос не подходит — надо его написать или детально знать особенности каждого провайдера (в том числе будущего). Второе на практике невозможно, поэтому единственный вариант — написали-протестировали-поправили.
1 программа, 2 бд как вы себе представляете править это независимо используя обращения к контексту?
нормально представляю. А вот как репозиторий поможет — не понимаю.

Можете пример привести?
я приводил пример, свой пожалуйста
Какой пример? Вы покажите код, в котором для разных баз генерируются разные запросы. И посмотрим что там дает репозиторий и какие есть альтернативы.
Если я правильно понял, топистартер пишет разные запросы для различных провайдеров, при этом необходимо менять провайдеров незаметно для бизнес-логики — вот он и выделил под каждый провайдер свои «репозитории». А как бы Вы решали подобную задачу?
именно, мы это и делаем, и причем успешно, да на старте были затыки с архитектурными решениями, но они окупились.
Взял бы ef и общее подмножество типов. А там где запросы кардинально отличаются подсовывал бы разные функции, возвращающие IQueryable. Собственно как выше и написал.
Да, реализация будет попроще, но вам все равно нужен слой между EF и BL, определяющий, какую конкретную функцию использовать?
Это не слой, а обычный сервис, который через IoC попадает в BL.
Напрмиер:
class SomeBL
{
    SomeBL(private IPlatformSpecificQuery queryService, MyDbContext context) {....}
    
    IQueryable<Entity> GetEntites() 
    {
         // some code 
         return queryService.PlatfomSpecificQuery1(context, ...);
    }
}


Вообще плодить много слоев — вредно. Сильно растут затраты на поддержку, когда надо добавлять параметры и расширять схему.
SomeBL(private IPlatformSpecificQuery queryService, MyDbContext context)
то есть вы не управляете жизнью контекста? Предоставляя открытый доступ к MyDbContext, печально,
IQueryable GetEntites() а дальше мы выставляем общий интерфейс с проблемами о которых описано. Круто, чего сказать.
IoC управляет. PerRequestInstance.

Про какие проблемы вы говорите я так и не понял.
Проблема это когда есть задача и её решение ведет к ухудшению характеристик приложения. То есть ваш репозиторий это проблема, потому что тормозит.
А в чем проблема IQueryable? В том, что его неправильно могут использовать? Это проблема в обучении, а не в коде.

Тем более вы же отдаете IEnumerable из репозитария, как вы заставите писать фильтры в базе (Query) вместо применения Where для IEnumerable?
Это проблема в обучении, а не в коде.
нет, это проблема MS которые решили, что можно спрятаться за правайдером с передачей любого выражения.
Видимо это вообще глобальная проблема — написать можно что угодно.
кстате а как ваш класс будет справляться с 18 параметрами, которые где то тут гуляют. Передадим это в функцию? (о да класс) а она передаст это в query? замечательно
Никак. Он уже отдал IQueryable, все критерии фильтрации применяются туда. Магия компонующихся функций.
ну и опять мы вернулись IQuariable, вы же сами указывали
If you have a specific ORM in mind, then be explicit about it. Don't hide it behind an interface. It creates the illusion that you can replace one implementation with another. In practice, that's impossible.
А никто ничего и не прячет. Мы честно говорим: вот EF, вот его интерфейс — именно его, а не абстрактный «репозиторий» — работайте.
Don't hide it behind an interface.
речь про IQuariable, за которым прячется конкретная орм,
И еще раз повторю: нет. Я уже выше приводил пример — проблема IQueryable в том, что между разными ORM его поведение отличается (может отличаться). Но если мы знаем, что с той стороны EF, то мы, по факту, имеем дело с одним и тем же IQueryable. До тех пор, пока мы не говорим «нет, у нас тут не EF, тут может быть любой QueryProvider» — мы ничего не прячем.
Согласен что AST (я походу подхватил, писать действительно проще чем expression tree) будет одно и то же, но вот провайдер может не суметь его обработать
Вот чтобы их переписать, там и стоит адаптер.
Я конечно может быть и загоняюсь, но есть Область задания функции, вы же согласны, что у разных провайдеров она разная, то есть мы может сформировать выражение так, что один провайдер сможет его обработать, а другой нет, более того можно нагородить такое, что не одному провайдеру не обработать. Я сторонник жестких ограничений, в том числе и синтаксических ( хотя это не всегда хороший подход, тк на его содержание может уйти много ресурсов ) и я плохо перевираю, когда ограничения нужно держать в голове ( других ограничителей просто нет ), а работая в команде приходиться это все держать не только в свой голове. О чем то можно забыть упустить и потом долго разбираться почему программа работает не так.
Любое наложенное ограничение упрощает вашу жизнь, как разработчика DAL, и усложняет жизнь программиста, которому с этим DAL интегрироваться. Вот и весь компромис.
Любое наложенное ограничение упрощает вашу жизнь, как разработчика DAL
да, а еще тестировщика, заказчика и начальства.
и усложняет жизнь программиста
да, но меньше вариантов получить по шее, за то что он по ( не знанию, лени и тд ) накосячил, за ним не до проверил тестировщик, из за этого заказчик пожаловался руководству.
Вот и весь компромис.
какой там. жесткая диктатура. Но пусть она остается в коде.
да, а еще тестировщика, заказчика и начальства.

А вот это иллюзия.

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

Это только в том случае, если вы уверены, что вы знаете бизнес лучше, чем этот разработчик.
Как результат — при необходимости выйти за рамки очерченные вашим репозиторием, привлекается сразу 2 программиста, прикладной и программист DAL.
Возможно, но я не думаю, что нужно иметь универсальных солдат, он и UI и DAL и еще много чего делаем, что касается доступ к разным БД, то этим явно должен заниматься человек, который в это погружен и знает ( или хотя бы догадывается ) где может возникнуть проблема.
Есть разница между прикладной программист сделал запросы сам (через IQueryable) и с каждым 10-м нужно разбираться потому что он тормозит и разбираться с каждым запросом прикладного программиста. Это банально удорожает и усложняет процесс разработки.
Прикладной программист сделал запросы сам (расширение реализации Repository) и с каждым 10-м нужно разбираться потому что он тормозит.
Я не много переписал, получил не большой прирост в цене (относительного написанного вами). С другой стороны у прикладного программиста меньше возможностей продублировать уже существующий запрос, за счет этого идет выигрыш.
А можно поподробнее, в чем выйгрыш то?

1) Запросы становится проще писать с репозитарием, по сравнению с IQueryable? Как то незаметно, скорее наоборот — формировать Query для репозитория гораздо более многословно, чем написать IQueryable запрос.
2) Декомпозиция запросов будет в обоих случаях.
3) Быстродействия репозиторий не прибавит, а скорее наоборот.
4) Количество изменений с репозиторием будет больше, так как репозиторий должен знать о прикладной логике приложения, чтобы хоть сколько нибудть быстро работать (нарушается принцип расслоения), а к IQueryable дописать предикат\пейджинг\сортировку\проекцию можно прямо по месту использования, не влезая в другие слои.

Эта тема больше недели существует, но ни одного явного преимущества репозитория не было показано (с примерами кода), а недостатки прут из всех щелей.
1) Запросы становится проще писать с репозитарием, по сравнению с IQueryable? Как то незаметно, скорее наоборот — формировать Query для репозитория гораздо более многословно, чем написать IQueryable запрос.
всегда можно использовать запись в виде Query.With(q=>q.Param = value).With…
3) Быстродействия репозиторий не прибавит, а скорее наоборот.
любая обертка ведет к потери производительности, это согласен, касательно производительности выполнения запросов, это будет зависеть только от того что написать в конкретной реализации
4) Количество изменений с репозиторием будет больше, так как репозиторий должен знать о прикладной логике приложения, чтобы хоть сколько нибудть быстро работать (нарушается принцип расслоения),
это ему знать не нужно, он должен знать о запросе в терминах предметной области и о построителе который умеет перегонять запрос в запрос к хранилищу, тк это фасад и он ничего сам не делает, только делегирует и объединяет. Вот ваше расслоение и единственность ответственности. Что касается количества изменений, да их будет больше, но любая попытка пере использования к этому ведет.
а к IQueryable дописать предикат\пейджинг\сортировку\проекцию можно прямо по месту использования, не влезая в другие слои.
да, быстро удобно, но только если у нас в приложении работа с 1 бд и только с ней, но если даже при наличии 1 бд нет ни какой гарантии что прикладной программист напишет адекватный запрос или хуже того начнет дублировать распространяя по коду (почему это плохо наверное понятно).
Эта тема больше недели существует
мне то же уже надоело,
всегда можно использовать запись в виде Query.With(q=>q.Param = value).With…

А кто-то рассказывал про вред свободных запросов, да. В итоге, как и говорилось, вы были вынуждены ввести ту же функциональность, что и в избегаемом вами LINQ. Или вы хотите сказать, что такие конструкции не дублируются по всему коду?
А кто-то рассказывал про вред свободных запросов,
это не свободный запрос, посмотрите внимательно, это всего лишь запись
query.Param = value; query.Param1 = value1;
запрос так и остался в терминах предметной области
что такие конструкции не дублируются по всему коду?

query.Paging.Skip = 10; query.Paging.Take = 10;
формирование запроса в терминах предметной области, не нужно делать универсальных запросов, их нужно избегать, иначе мы получим собственный IQuariable.
запрос так и остался в терминах предметной области

В LINQ запрос тоже в терминах предметной области — если, конечно, у вас хранилище снаружи представляет объекты предметной области.
нет, я могу писать
Query.ActiveUsers
что в терминах хранилища на том же LINQ будет
Where(user=>user.isBanned == false && user.LastLogining < now.AddYear(-1) && user.isDeleted == false)

А вы считаете, что запрос «пользователь не забанен, и пользователь логинился не далее года назад, и пользователь не удален» — не в терминах предметной области? Термины «забанен» и «удален» — не из предметной области внезапно?
Предметно, но вот я хочу получить всех активных пользователей, я так и пишу, и ни где снаружи нет как их получить, я пишу не «как» а «что». Плюс со временем то «как» формировать запрос может измениться, добавиться свойства, удалиться, поменяться во вне изменений не будет.
Но что помешает вашему прикладному программисту написать Query.With().With().With() для достижения тех же целей?
Ничего, поэтому запросы должны формироваться исходя из бизнес требований, а не по каждому свойству в отдельности
Но у вас есть возможность сформировать запросы по каждому свойству в отдельности?
А кто мешает сделать extension метод для IQueryablr, который делает такой же запрос? Зачем городить репозитории?
Ничего, но вы напрочь игнорите тему, а тема у нас «Жизнь приложения при встрече нескольких бд». static очевидно просто подменить во время выполнения…
static очевидно просто подменить во время выполнения…

Вообще — просто. Service Locator прекрасно справляется.

(хотя на самом деле можно и QueryProvider от нижележащего запроса анализировать)
мы уходим от сути шаблона, это не только запросы
С точки зрения потребителя — только запросы. Не важно, что внутри реализации.
Я уже больше одного раза писал что надо сделать если хочется подменять. И репозитории для этого не нужны.
По мимо запросов еще есть
1. Создание объекта
2. Сохранение
3. Объекты бизнеса могут отличаться от хранимых — это маппинг
Где все это делать?
1. Создание объекта

В бизнес логике вызывать new и add или что ты имеешь ввиду?

2. Сохранение

Вызывать SaveChanges в бизнес-логике.

3. Объекты бизнеса могут отличаться от хранимых — это маппинг

EF делает маппинг. Если тебе нужна принципиально другая структура для расчетов — например граф — то это тоже часть бизнес-логики.

В принципе вопрос не понятен. Все что написал выше все равно придется делать. Даже если ты завернешь эти функции в репозитарий, то БЛ будет вызывать репозитаий чтобы создавать объекты, сохранять в базу итп. Так что какой профит от репозитария непонятно совершенно.
всегда можно использовать запись в виде Query.With(q=>q.Param = value).With…

Тогда у вас получится тот же IQueryable.

4) Количество изменений с репозиторием будет больше, так как репозиторий должен знать о прикладной логике приложения, чтобы хоть сколько нибудть быстро работать (нарушается принцип расслоения),

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

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

В вашем случае это выражается в разных классах Query для разных запросов и разных обработках этого Query внутри репозитория. Простой пример — был у вас Query без пейджинга, а вы захотели добавить — вам надо поправить и Query и билдер (как часть репозитория). А с IQueryable можно прямо в контроллере сделать пейджинг.

Что касается количества изменений, да их будет больше, но любая попытка пере использования к этому ведет.
Не любая. IQueryable+методы-комбинаторы+generics не приводят к увеличению площади изменений.
нет ни какой гарантии что прикладной программист напишет адекватный запрос

Репозиторий такую гарантию не дает. тем боле ты сам предлагаешь Linq-подобный интерфейс. Более того, отдавая IEnumerable ты никак не мешаешь разработчику написать любой linq запрос к данным в памяти, что менее эффективно любого неадекватного запроса.

хуже того начнет дублировать распространяя по коду

Репозиторий также не страхует от копипасты.

Все преимущества исключительно надуманные или требуют высокой дисциплины разработчиков, но при высокой дисциплине и с IQueryable проблем нет.
Тогда у вас получится тот же IQueryable.
это не IQuariable а Linq подобный, передавать в реп все равно QueryObject
Не любая. IQueryable+методы-комбинаторы+generics не приводят к увеличению площади изменений.
методы-комбинаторы+generics ну да, они бесплатны, и мы не как не можем оградитьмя от дублирования путем не использования всего это го
Репозиторий такую гарантию не дает. тем боле ты сам предлагаешь Linq-подобный интерфейс.
QueryObject является ограничителем
Все преимущества исключительно надуманные или требуют высокой дисциплины разработчиков, но при высокой дисциплине и с IQueryable проблем нет.
как раз на оборот, из за того что за всеми не уследишь, репозиторий повышает контроль, правда за счет ограниченности
это не IQuariable а Linq подобный, передавать в реп все равно QueryObject

Если вы позволяете передавать Expression Tree в вашем Query, то у вас появляются те же «проблемы», которые есть у IQueryable.

QueryObject является ограничителем

Не является, у вас репозитарий отдает IEnumerable. Кто помешает те же Linq методы вызвать для него?

как раз на оборот, из за того что за всеми не уследишь, репозиторий повышает контроль, правда за счет ограниченности

Ничего не повышает, а делает только хуже, так как отдает IEnumerable.
Если вы позволяете передавать Expression Tree в вашем Query, то у вас появляются те же «проблемы», которые есть у IQueryable.

это не дерево, а форма записи, простейший вариант
public static T With<T>( T target, Action<T> action )
        {
		action( target );
		return target;
	}

Не является, у вас репозитарий отдает IEnumerable. Кто помешает те же Linq методы вызвать для него?
ни кто, действительно ни кто, но вот нет запроса GetAll, есть один плюс IEnumerable будет одинаково фигово отрабатывать на больших выборках на всех бд, что сразу всплывет, если же выборка не большая, то этого ни кто не заметит, если такой запрос единичный, то наверное это не страшно ( хотя я не приветствую ). Другой сценарий при использовании paging, это как надо задать сколько элементов нужно пропустить и сколько взять, что бы после фильтрации IEnumerable осталось нужное количество объектов, ну наверное пропустить 0 взять 1000000 ( за такой запрос медаль только за храбрость и дадут ), но это случай с большими выборками ( разобрали ).
вот нет запроса GetAll

А как же решается простейший сценарий использования «хочу посмотреть все что-нибудь в табличном отображении»?
я не правильно написал GetAll есть только у тех репозиториев у которых не так много данных
А как посмотреть в табличном отображении все заказы?
это не дерево, а форма записи, простейший вариант

Это никак не уменьшает объем писанины по сравнению с IQueryable. В совокупности с билдерами даже увеличивает.

но вот нет запроса GetAll

Ну это банальное вранье или самообман.

Во-первых можно передать пустой запрос и в этом случае ни один из билдеров не сработает и будет втянута вся таблица.
Во-вторых почти всегда есть кейс — показать все записи в UI в виде таблицы или четь в этом роде
Во-третьих, даже если вы требуете передавать параметры пейджинга, то никто не помешает передать размер страницы равный int.MaxValue.

Сколько я не видел репозиториев — почти все позволяли втянуть всю таблицу, а разработчики этим активно пользовались.
Это никак не уменьшает объем писанины по сравнению с IQueryable. В совокупности с билдерами даже увеличивает.
я не разу ни где не писал, что объем уменьшиться
Во-первых можно передать пустой запрос и в этом случае ни один из билдеров не сработает и будет втянута вся таблица.
пустые запросы можно отслеживать
Во-вторых почти всегда есть кейс — показать все записи в UI в виде таблицы или четь в этом роде
если записей не много то да. Вы реально хотите увидеть таблицу в UI из 999999 ( да же не миллион ) записей?
Во-третьих, даже если вы требуете передавать параметры пейджинга, то никто не помешает передать размер страницы равный int.MaxValue.
вы вообще читаете то что я вам пишу?
ну наверное пропустить 0 взять 1000000 ( за такой запрос медаль только за храбрость и дадут ),

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

Мы только на пейджинг посмотрели, оказывается при всей невероятной обороне легко сделать запрос на миллион строк, а если там еще и джоины, то система уйдет в глубокий даун.

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

И это при том что кода больше и оно уже тормознее работает из-за отсутствия проекций.
Sign up to leave a comment.

Articles