Entity Framework или почему я реализую Repository

    Абстракции? Кому нужны абстракции?
    EF наикрутейшая ORM от MS. habrahabr.ru/post/157267 это действительно круто. Разработать такую систему очень затратно по всем направлениям. CodeFirs вещь, знания проектирования БД, да какой там! Не нужны сами знания SQL – и это круто. Но так ли все радужно?
    Julie Lerman может много рассказать msdn.microsoft.com/en-us/magazine/ff898427.aspx
    image
    Ничего не напоминает?
    Это реализация Repository + UoW. Так значит, при использовании EF делать всевозможные обертки бесполезно?
    EF безумно популярная технология, не найти нужного провайдера под свое хранилище затруднительно.
    Основная идея EF ( как и любого Repository ) – отделение предметного слоя от слоя хранения. Но зачем?
    Все просто – что бы без болезненно изменять хранилище и предметку не зависимо.
    Когда нужно менять хранилище?

    Хотелось бы что бы никогда, но как говориться «никогда не говори..». Я работаю над проектом, в котором два боевых слоя хранилища и еще одно демонстрационное. Клиент сам выбирает, как он будет хранить свои данные.
    Нужно ли строить абстракцию поверх абстракции и делать то, что уже и так реализовано?

    Ответ лежит в совместимости конкретных провайдеров. Вот что о совместимости говорит один из разработчиков таких провайдеров Yevhen Shchyholyev www.infoq.com/articles/multiple-databases. Проблемы которые были понятны на интуитивном уровне укрепляются самими разработчиками.
    Вот некоторые из них

    Типы данных


    Entity Framework поддерживает ограниченный набор .NET типов (signed integers, Single, Double, Decimal, String, Byte[], TimeSpan, DateTime, DateTimeOffset, Boolean, Guid). Однако не все.NET типы из этого списка поддерживаются базами данных.

    Типы – вечная проблема. То есть один сценарий, выполненный на одном провайдере ( именно на провайдере ) может не дать тех же результатов на другом.
    Поддержка DateTimeOffset

    DataTimeOffset — самый проблематичный тип.NET. Он поддерживается в SQL Server MS 2008, Oracle и SQLite, но не в SQL Server MS 2005, MySQL или PostgreSQL, так как в этих базах данных отсутствует DateTime, который может сохранить смещение часового пояса.

    Проблема возникшая у нас, дата храниться в UTC, но EF под SQLight упорно возвращал в Local. Проблема легко была решена, тк мы используем Mapping.
    Производительность

    Если специфические особенности определенной базы данных не приняты во внимание, возможно, что LINQ-запрос может быть обработан быстрее в одной базе данных и медленнее в другой.
    Разбивка на страницы данных, выполненная LINQ-методы .Skip ()/.Take(), может быть одной из самых затратных операций. В отличие от в SQL Server, Skip /Take, Oracle, генерирует два подзапроса с сортировкой во внутреннем подзапросе, который может негативно повлиять на производительность. Нужно отметить, что разбивка на страницы может быть довольно неэффективной в других базах данных, особенно если сортировка выполнена по неиндексируемыми столбцами.

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

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

    Здесь ясно сказано mission impossible. SQL SQLю рознь, провайдер провайдеру. Тут даже речь не о том, что запрос будет не эффективен, а о том, что он может покрошить выполнение приложения.
    Соответствующий раздел MSDN говорит, что все эти функции поддерживаются всеми EF-провайдерами:
    «В этом разделе рассматриваются канонические функции, которые поддерживаются всеми провайдерами данных и могут использоваться всеми технологиями запросов».
    Этот раздел, кажется, сверхоптимистичен, поскольку различные DBMS обладают различной функциональностью, и иногда невозможно реализовать все возможности для совместимости.

    Вот ссылка. Но не абы кто, а ведущий разработчик набора сторонних провайдеров говорит mission impossible.
    Заключение

    В этой статье я попытался предоставить краткое описание наиболее распространенных проблем в процессе разработки EF-приложений для не Microsoft SQL Server баз данных. Несомненно, много других аспектов не получили внимания в этой статье...

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

    Подводя итоги.


    EF это хорошая абстракция, и как любая абстракция, она не лишена главного недостатка – ее реализации. Проблемы описанные в этой статье взяты не из головы и не из уст скептиков, а из первых рук – разработчика провайдеров.
    Провайдеры разрабатываются разными фирмами и я уверен, что если в одном проекте использовать провайдеры от разных производителей проблем с совместимостью будет гораздо больше. forums.mysql.com/read.php?174,614148,614148
    Я ни в коем случае не говорю НЕТ EF, я говорю да Repository, ведь все описанные проблемы легко обойти с помощью этой абстракции которая обернет EF и поглотит проблемы совместимости.

    P. S.


    Рассмотрен лишь технологический пример для чего может понадобиться реализовывать «Велосипед», причин по которым можно и нужно использовать репозиторий и примочки гораздо больше.
    Share post

    Comments 269

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

                    Кому БД должна? БД должна хранить и возвращать так, как указано в её документации.
        0
        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, ограничив набор поддерживаемых запросов и доведя это до тех, кто будет наш репозиторий использовать.
          0
          Как выяснилось, запрос, который строил EF, содержал 8 вложенных select-выражений!

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

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

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

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

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

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

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

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

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

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

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

                    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 и поглотит проблемы совместимости.
                      0
                      Вы увидели свет в конце тоннеля, луч надежды, но не заметили что он не для вас, тк читаете между строк.
                      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 который знает о проблемах конкретных провайдеров и борется только с ними а не с общими проблемами ОРМ.
                        0
                        Тогда зачем вы скрываете конкретную ORM (EF) за интерфейcом (репозиторием)?
                          0
                          да интерфейс общий, но используется ограниченный набор запросов, отражающий запросы предметной области, и для меня важно что изменения в запросах к одной бд не поломают запросы к другим, тк переводом запросы предметной области переводятся в запросы к конкретной ОРМ, конкретной реализацией репозитория.
                            0
                            используется ограниченный набор запросов

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

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

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

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


                                                            Я вам продемонстрировал, как подход «доступно все, что описано в предметной области» не ограждает от неэффективных запросов.
                                    0
                                    Совсем необязательно урезать доступную коду-потребителю функциональность. Просто в интерфейсе репозитория можно оставить (унаследовать или проксировать) стандартный интерфейс, а для критичных запросов иметь отдельные. Или реализовать оптимизацию запросов под конкретного провайдера как декоратор — перед выполнением запроса проверять его на принадлежность к некоему множеству и выполнять «нативный» код, если принадлежит к нему, а если нет, то передавать стандартному.
                                      0
                                      Не обязательно. Но автор статьи пошел именно таким путем.
                                        0
                                        Совсем необязательно урезать доступную коду-потребителю функциональность
                                        я согласен
                                        а для критичных запросов иметь отдельные
                                        а как узнать какой запрос критический если у нас «доступную коду-потребителю функциональность» есть доступ и тут можно пилить что угодно и как угодно.
                        0
                        Ничего не напоминает? Это реализация 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. Но предположим на минуту, что вы правы, и эта задача действительно не имеет решения. Как же репозиторий ее решает в таком случае?
                          +1
                          >>EF — это не репозиторий.
                          Ну чем же DbContext не репозиторий? Классик, который наружу выставляет коллекции _доменных_ объектов с которыми можно работать как с обычнми коллекциями + немного UoW.
                            0
                            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]
                              +1
                              С другой стороны там же написано [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.
                                0
                                Т.е. концептуально EF конечно не репозиторий, поскольку репозиторий более окрашен в «тона предметной области». Так же, как и Specification по сравнению с Query Object, как мне кажется.

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

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

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

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

                                              В этом сценарии, правда, надо очень четко следить за жизненным циклом контекста, чтобы в нем не было «лишних» изменений, которые провалятся в БД незаметно для программиста. Идеальный вариант — открыть контекст по месту использования, внести нужные изменения, сохранить, закрыть.
                                                0
                                                Т.е. получается что-то вроде:
                                                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 и Репозиторий?
                                                  0
                                                  Намного проще.

                                                  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
                                                    Мне почему-то кажется, что это не намного проще. Если я правильно понимаю, это замена композиции наследованием? И здесь меня смущают следующие моменты.
                                                    1. Ограничение доступа к DbContext-у очень условное: т.е. можно конечно коду-потребителю передавать IFooDbContext и там будет только то, что мы туда внесли. Но ведь пытливый разработчик может всегда привести его к типу FooDbContext и получить тот же DbSet и снова начать плодить «зверинец» разнообразных запросов. Конечно, в случае замены наследования композицией можно тоже взять Reflection и сделать все, что хочется, но это уже явный «хак», на который просто так никто не решится.
                                                    2. Что если мы захотим написать unit tests к методам GetFooByParamSet1 и т.д.? Как мы заменим сам DbContext каким-нибудь mock-объектом?
                                                      0
                                                      Нет, это не замена композиции наследованием. Это сохранение функциональности в изначально ответственной за нее сущности. Я напомню, что использование наследника 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 — никак. Какой-то из уровней абстракции тестировать уже не нужно. Если вы хотите написать тесты на этот уровень, и при этом это реально оправданно, а не блажь, значит, у вас ошибка в разделении обязанностей.

                                                        0
                                                        Я правильно понимаю, что через интерфейс IFooDbContext вы отдаете потребителям IQueryable<A> и IQueryable<B>?
                                                        interface IFooDbContext
                                                        {
                                                          IQueryable<A> A {get;}
                                                          IQueryable<B> B {get;}
                                                        }
                                                        
                                                        Если так, то как мы решаем проблему «зверинца» запросов от потребителей? Хочется ведь их свести вместе и ограничить-таки интерфейс DbContext-а.
                                                          0
                                                          Интерфейс мы уже ограничили — наружу выставлено только то, что нам надо. А унификация запросов, если это необходимо, делается либо через методы-расширения на IQueryable, либо через построители для expression.
                                                            +1
                                                            Выставить наружу IQueryable это довольно слабое ограничение. Множество всевозможных запросов, построенных с его помощью, со временем может очень вырасти. Унификация с помощью методов-расширений к IQueryable, как я понимаю, не ограничит разнообразие запросов: ведь я по-прежнему могу писать запросы на «чистом» LINQ. А насчет «построителя для expression» что имеется ввиду? И главное, как это ограничит для потребителей возможность писать чистые LINQ-запросы?
                                                              0
                                                              А меня не пугает множество запросов. До тех пор, пока они покрывают бизнес-требования, по большому счету, все равно, как они написаны. Как следствие, у меня нет задачи отобрать у пользователя возможность работать с LINQ (более того, у меня как раз есть задача ее сохранить, потому что тогда я упрощу интеграцию с кучей полезных вещей), у меня есть задача упростить его работу в типовых сценариях.
                                                                0
                                                                Тогда получается, что если мы используем EF в качестве ORM (или, кстати говоря, C# LINQ driver над MongoDb), то репозиторий и не нужен? Или есть все-таки какие-то условия, при которых нам стоит ввести репозиторий и скрыть ORM и в том числе IQueryable?
                                                                  0
                                                                  Да, именно так и получается. Именно об этом пишет, например, Симан в статье, на которую вы же и ссылаетесь: если вы используете конкретный ORM, и не планируете использовать их несколько (а это, будем честными, очень редкая ситуация) — не надо пытаться этот ORM скрыть за другим паттерном.
                                                                    +1
                                                                    Понятно. Но насчет Симана я не соглашусь: по-моему Вы отделяете приведенное Вами ранее высказывание от контекста.
                                                                    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.

                                                                    Только это уже какая-то герменевтика пошла…
                                                                      0
                                                                      Во-первых, статья называется «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?)
                                                                        0
                                                                        Нет, ее посыл в том, что 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, с теми же проблемами. Вопрос только в том, всегда ли нужно такое разнообразие и те возможности, о которых Вы написали.
                                                                          0
                                                                          На всякий случай напомню, что я написал почти то же самое:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

                                                                        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();
                                                                          }
                                                                        }
                                                                        
                                                                          0
                                                                          Нет!
                                                                          public IEnumerable GetPaged(int skip, int take)

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

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

                                                                            Пожалуйста, покажите, как в вашем случае реализуется описанный мной сценарий (необходимость постраничного вывода, две СУБД, одна поддерживает Skip, другая — нет). Меня интересует структура задействованных классов и конкретные точки расширения, которые вы используете, чтобы обойти проблему.
                                                                              0
                                                                              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 параметров в функцию?
                                                                                0
                                                                                Так в каком же конкретно месте происходит выбор специфичного для конкретной БД способа пейджинга? Если в QueryBuilder, то, удивительным образом, репозиторий для этого не нужен.

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

                                                                                                У вас свой 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                                      Типы данных

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

                                                      Поддержка DateTimeOffset

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

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

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

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

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


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

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

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