Pull to refresh
15
0
Сергей Вершинин @SVVer

Разработчик

Send message
Тогда получается, что если мы используем EF в качестве ORM (или, кстати говоря, C# LINQ driver над MongoDb), то репозиторий и не нужен? Или есть все-таки какие-то условия, при которых нам стоит ввести репозиторий и скрыть ORM и в том числе IQueryable?
Выставить наружу IQueryable это довольно слабое ограничение. Множество всевозможных запросов, построенных с его помощью, со временем может очень вырасти. Унификация с помощью методов-расширений к IQueryable, как я понимаю, не ограничит разнообразие запросов: ведь я по-прежнему могу писать запросы на «чистом» LINQ. А насчет «построителя для expression» что имеется ввиду? И главное, как это ограничит для потребителей возможность писать чистые LINQ-запросы?
Вы имеете ввиду, что набор возможных запросов к репозиторию со временем разрастается так, что лучше иметь IQueryable или его аналог?
Я правильно понимаю, что через интерфейс IFooDbContext вы отдаете потребителям IQueryable<A> и IQueryable<B>?
interface IFooDbContext
{
  IQueryable<A> A {get;}
  IQueryable<B> B {get;}
}
Если так, то как мы решаем проблему «зверинца» запросов от потребителей? Хочется ведь их свести вместе и ограничить-таки интерфейс DbContext-а.
Мне почему-то кажется, что это не намного проще. Если я правильно понимаю, это замена композиции наследованием? И здесь меня смущают следующие моменты.
  1. Ограничение доступа к DbContext-у очень условное: т.е. можно конечно коду-потребителю передавать IFooDbContext и там будет только то, что мы туда внесли. Но ведь пытливый разработчик может всегда привести его к типу FooDbContext и получить тот же DbSet и снова начать плодить «зверинец» разнообразных запросов. Конечно, в случае замены наследования композицией можно тоже взять Reflection и сделать все, что хочется, но это уже явный «хак», на который просто так никто не решится.
  2. Что если мы захотим написать unit tests к методам GetFooByParamSet1 и т.д.? Как мы заменим сам DbContext каким-нибудь mock-объектом?
Т.е. получается что-то вроде:
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 и Репозиторий?
Тогда все же хочется понять Ваш подход.
Пусть мы имеем РСУБД, даже конкретно SQL Server, и храним в ней данные модели предметной области. Для точности пусть это будут DDD-агрегаты типа Foo с какой-то внутренней структурой. Для маппинга используем EF и получаем объекты Foo через DbSet<Foo>.
В то же время обобщенный интерфейс DBSet-ов нас не устраивает в том плане, что в разных местах системы, где он используется будет «зверинец» LINQ-запросов, в том числе и повторяющихся. Логично, вроде бы, в этом случае ввести над DbSet<Foo> какой-то репозиторий FooRepo, в интерфейсе которого будет ограниченное число методов доступа к данным, которых достаточно для работы всего вышестоящего кода в системе. Но в то же время нам нужен UoW, чтобы мы могли модифицировать объекты группам и рамках одной транзакции. Как добавить этот UoW?
Как бы Вы подошли к решению такой задачи?
Пришлось перечитать несколько раз. Автору стоило бы как-то выделить, на мой взгляд. Просто человеку ведь свойственно выделять из множества информации ту, которая его беспокоит больше. Вот так же вышло и со мной.
А где я сказал, что репозиторий поможет решить эти проблемы?
Похоже я в своем первом посте выразил слишком общее одобрение всему сказанному в статье. Моя вина, каюсь! Но попробую пояснить: подавляющая часть статьи содержит описание проблем, с которыми можно столкнуться при, скажем так, использовании EF «в лоб», без должного понимания. Именно к этому описанию я и попытался добавить некоторое дополнение.
А вот это высказывание я поддержать не готов:
я говорю да Repository, ведь все описанные проблемы легко обойти с помощью этой абстракции которая обернет EF и поглотит проблемы совместимости
Оно вообще похоже на то, что найдена «серебряная пуля». Но мне кажется, что здесь свое дело сделали эмоции автора, ведь и стиль изложения материала в статье несколько «импульсивный», имхо. Насчет Repository над EF не хочу писать обрывками: думаю, идея полезная и может решить часть проблем, но каких и как — нужно пояснить. Попробую сформулировать это чуть позже.
Как-то Вы мой пример, который приведен лишь для того, чтобы обратить внимание на возможные последствия (чаще всего, по незнанию того, «что происходит на два уровня ниже твоего уровня абстракции»), вырвали из контекста и за него меня «хлещете». Он не направлен на то, чтобы как-то упрекнуть EF. Это как раз пример того, как непонимание механизмов внутренней работы (в данном случае мое) может привести к проблеме.
Отлично! Я ведь с этого и начал:
… чтобы те, кто рассматривает возможность использования EF или других ORM в первый раз, понимали, с какими ограничениями они столкнутся в будущем, и учитывали это в своих проектных решениях
В общем да. Но только как в случае с созданным поверх EF репозиторием реализовать тот же UoW, не внося путаницы уже в наш репозиторий?
Во-первых сам EF ничего не строит сам, он только интерпретирует ваш запрос
Конечно. Но только интерпретировать он его может по-разному. И получается, что помимо LINQ я должен понимать особенности интерпретации expression tree с запросом каждым конкретным провайдером. Т.е. абстракция уже не скрывает от меня детали реализации, а значит она «течет».
То есть проблема не в том, что EF плохой
Я не говорил, что EF плохой! EF отличный, просто нужно понимать, что он не избавляет от необходимости хорошо понимать его работу с СУБД. Вы, кстати, наглядно аргументировали это в своем комментарии.
Что именно вы считаете фасадом EF?
В данном случае я имею ввиду DbSet<T>, который, реализуя IQueryable<T> и добавляя Add и Remove, фактически и действует как
… in-memory domain object collection
С другой стороны там же написано [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.
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, ограничив набор поддерживаемых запросов и доведя это до тех, кто будет наш репозиторий использовать.
А можно поинтересоваться, чем микросервисная архитектура отличается от сервис-ориентированной архитектуры (SOA)
И дальше два варианта: либо вы инъектируете в A и B репозиторий, передадите ему спецификацию и получите данные, либо какой-то внешний код вызовет A.GetSpecification(...) и B.GetSpecification(...), после чего запросит у репозитория данные и подсунет их через A.SetData(...) и B.Set(...). Во втором варианте, имхо, код будет запутаннее, т.е. получится ровно то, против чего возражает автор статьи.
Так IQueryable и будет у вас интерфейсом репозитория, только до такой степени общим, что любой репозиторий может быть его реализацией (правда, только в запросной части). В статье же предлагается передать в A и B сами данные
А зачем дополнительно передавать смысл в трех словах, если само определение состоит из трех слов и наиболее точно выражает смысл?

Information

Rating
Does not participate
Location
Уфа, Башкортостан(Башкирия), Россия
Registered
Activity