Почему я не должен использовать шаблон хранилища с Entity Framework?

203

Во время собеседования меня попросили объяснить, почему шаблон репозитория не подходит для работы с ORM, такими как Entity Framework. Почему это так?

StringBuilder
источник
60
это был вопрос с подвохом
Ому
2
Я бы, вероятно, ответил интервьюеру, что Microsoft очень часто использует шаблон репозитория, пока они демонстрируют структуру сущностей: | ,
Лоран Бурго-Рой
1
Так в чем причина того, что интервьюер не был хорошей идеей?
Боб Хорн
3
Забавно, что поиск «шаблона репозитория» в Google дает результаты, которые в основном связаны с Entity Framework и тем, как использовать шаблон с EF.
Арсений Мурзенко
2
проверьте блог ayende ayende.com/blog . Основываясь на том, что я знаю, он использовал шаблон репозитория, но в конце концов отказался от него в пользу шаблона объекта запроса
Jaime Sangcap

Ответы:

99

Я не вижу причин для того, чтобы шаблон Repository не работал с Entity Framework. Шаблон репозитория - это уровень абстракции, который вы помещаете в слой доступа к данным. Ваш уровень доступа к данным может быть чем угодно, от чисто хранимых процедур ADO.NET до Entity Framework или файла XML.

В больших системах, где у вас есть данные, поступающие из разных источников (база данных / XML / веб-сервис), хорошо иметь уровень абстракции. Шаблон репозитория хорошо работает в этом сценарии. Я не верю, что Entity Framework является достаточной абстракцией, чтобы скрыть то, что происходит за кулисами.

Я использовал шаблон Repository с Entity Framework в качестве метода уровня доступа к данным, и мне еще предстоит столкнуться с проблемой.

Еще одним преимуществом абстрагирования DbContextс помощью репозитория является модульная тестируемость . У вас может быть свой IRepositoryинтерфейс, в котором есть 2 реализации, одна (реальный репозиторий), который используется DbContextдля связи с базой данных, и вторая, FakeRepositoryкоторая может возвращать объекты в памяти / проверенные данные. Это делает ваш IRepositoryюнит-тестируемым, таким образом, другие части кода, которые используют IRepository.

public interface IRepository
{
  IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
  private YourDbContext db;
  private EFRepository()
  {
    db = new YourDbContext();
  }
  public IEnumerable<CustomerDto> GetCustomers()
  {
    return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
  }
}
public MockRepository : IRepository
{
  public IEnumerable<CustomerDto> GetCustomers()
  {
    // to do : return a mock list of Customers
    // Or you may even use a mocking framework like Moq
  }
}

Теперь, используя DI, вы получаете реализацию

public class SomeService
{
  IRepository repo;
  public SomeService(IRepository repo)
  {
     this.repo = repo;
  }  
  public void SomeMethod()
  {
    //use this.repo as needed
  }    
}
Shyju
источник
3
Я не сказал, что это не будет работать, я также работал с шаблоном репозитория с EF, но сегодня меня спросили, почему НЕ ХОРОШО использовать шаблон с DataBase, приложением, которое использует Database
2
Хорошо, так как это самый популярный ответ, я выбрал его в качестве правильного ответа
65
Когда в последний раз это был самый популярный вариант?
HDave
14
DbContext уже является хранилищем, хранилище предназначено для низкоуровневой абстракции. Если вы хотите абстрагироваться от разных источников данных, создайте объекты для их представления.
Даниэль Литтл
7
ColacX. Мы попробовали именно это - DBcontext прямо на уровне контроллера - и возвращаемся к модели репо. С помощью шаблона Repo юнит-тесты пошли от массивного насмешки над DbContext, который постоянно терпел неудачу. EF было сложным в использовании и хрупким и стоило часов исследований на нюансы EF. Теперь у нас есть небольшие простые макеты репо. Код чище. Разделение работы понятнее. Я больше не согласен с толпой, что EF - это уже шаблон репо и уже тестируемый модуль.
Rhyous
435

Единственная лучшая причина не использовать шаблон хранилища с Entity Framework? Entity Framework уже реализует шаблон хранилища. DbContextэто ваше UoW (единица работы), и каждый DbSetявляется хранилищем. Реализация другого уровня поверх этого не только избыточна, но и усложняет обслуживание.

Люди следуют шаблонам, не понимая цели шаблона. В случае с шаблоном хранилища цель состоит в том, чтобы абстрагировать логику запросов базы данных низкого уровня. В старые времена написания операторов SQL в вашем коде шаблон хранилища был способом вывести этот SQL из отдельных методов, разбросанных по всей вашей кодовой базе, и локализовать его в одном месте. Наличие ORM, такого как Entity Framework, NHibernate и т. Д., Является заменой этой абстракции кода и, как таковое, устраняет необходимость в этом шаблоне.

Тем не менее, неплохо создать абстракцию поверх вашего ORM, просто не так сложно, как UoW / repository. Я бы выбрал шаблон службы, где вы создаете API, который ваше приложение может использовать, не зная и не заботясь о том, поступают ли данные из Entity Framework, NHibernate или Web API. Это намного проще, поскольку вы просто добавляете методы в класс обслуживания для возврата данных, необходимых вашему приложению. Например, если вы пишете приложение To-do, вам может потребоваться сервисный вызов для возврата товаров, которые должны быть на этой неделе и еще не завершены. Все ваше приложение знает, что если оно хочет эту информацию, оно вызывает этот метод. Внутри этого метода и в вашем сервисе в целом вы взаимодействуете с Entity Framework или чем-то еще, что вы используете. Затем, если позже вы решите переключить ORM или получить информацию из веб-API,

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

Крис Пратт
источник
68
Похоже, это единственный правильный ответ.
Майк Чемберлен
10
Вы можете издеваться над DbContextEF6 + (см. Msdn.microsoft.com/en-us/data/dn314429.aspx ). Даже в меньших версиях, вы можете использовать поддельные DbContext-like класс с издевался DbSetс, так как DbSetреализует iterface, IDbSet.
Крис Пратт
14
@TheZenker, возможно, вы не точно следовали шаблону хранилища. Самая строгая разница - возвращаемое значение. Репозитории возвращают запрашиваемые значения, тогда как сервисы должны возвращать перечислимые. Даже это не совсем то, что черное и белое, поскольку там есть некоторые совпадения. Это больше в том, как вы используете это. Репозиторий должен просто возвращать набор всех объектов, к которым вы затем будете запрашивать, а служба должна возвращать окончательный набор данных и не должна поддерживать дальнейшие запросы.
Крис Пратт
10
На риск звучит эгоистично: они не правы. Теперь, что касается официальных руководств, Microsoft отказалась от использования репозиториев из того, что я видел со времен EF6. Что касается книги, я не могу сказать, почему автор решил использовать репозитории. Я могу говорить, как кто-то в окопах, строящих крупномасштабные приложения, что использование шаблона хранилища в Entity Framework - это кошмар обслуживания. Как только вы перейдете к чему-то более сложному, чем несколько репозиториев, вы в конечном итоге потратите непомерное количество времени на управление своими репозиториями / единицей работы.
Крис Пратт
6
У меня обычно есть только один сервис на базу данных или метод доступа. Я использую универсальные методы для запроса нескольких типов сущностей из одного и того же набора методов. Я использую Ninject, чтобы внедрить свой контекст в мой сервис, а затем мой сервис в мои контроллеры, чтобы все было аккуратно и аккуратно.
Крис Пратт
45

Вот один из вариантов Ayende Rahien: « Архитектура в яме гибели»: зло уровня абстракции хранилища

Я еще не уверен, согласен ли я с его заключением. Это ловушка-22 - с одной стороны, если я оберну свой EF Context в репозитории для конкретного типа с помощью методов поиска данных для конкретного запроса, я на самом деле смогу выполнить модульное тестирование своего кода (своего рода), что практически невозможно с Entity Рамки одни. С другой стороны, я теряю способность выполнять богатые запросы и семантическое поддержание отношений (но даже когда у меня есть полный доступ к этим функциям, мне всегда кажется, что я хожу по скорлупе вокруг EF или любой другой ORM, которую я могу выбрать поскольку я никогда не знаю, какие методы может поддерживать или не поддерживать его реализация IQueryable, будет ли он интерпретировать мое добавление в коллекцию свойств навигации как создание или просто как ассоциацию, будет ли она загружаться лениво или активно или вообще не загружаться по умолчанию и т. д., так что, возможно, это к лучшему. Объектно-реляционное «отображение» с нулевым импедансом является чем-то вроде мифологического существа - возможно, именно поэтому последний выпуск Entity Framework был назван «Волшебный единорог»).

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

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

Обновление: у меня был некоторый успех при использовании поставщика Effort для Entity Framework. Effort - это провайдер в памяти (с открытым исходным кодом), который позволяет вам использовать EF в тестах точно так же, как вы используете его для реальной базы данных. Я рассматриваю возможность переключения всех тестов в этом проекте, над которым я работаю, чтобы использовать этого провайдера, поскольку кажется, что все намного проще. Это единственное решение, которое я нашел до сих пор, которое решает все проблемы, о которых я говорил ранее. Единственное, что есть небольшая задержка при запуске моих тестов, поскольку это создает базу данных в памяти (для этого используется другой пакет под названием NMemory), но я не вижу в этом реальной проблемы. Есть статья Code Project, в которой говорится об использовании Effort (вместо SQL CE) для тестирования.

luksan
источник
3
Любая статья по архитектуре без упоминания модульного теста автоматически отправляется для меня в корзину. Одной из точек паттерна репозитория является получение некоторой способности к тестированию.
Спящий Смит
3
У вас все еще могут быть модульные тесты без переноса EF Context (который уже является репозиторием). Вы должны проводить модульное тестирование своего домена / сервисов, а не запросов к базе данных (это интеграционные тесты).
Даниэль Литтл
2
Тестируемость EF значительно улучшилась в версии 6. Теперь вы можете полностью высмеивать DbContext. В DbSetлюбом случае , вы всегда можете издеваться , и это в любом случае основа Entity Framework. DbContextэто немного больше, чем класс для размещения ваших DbSetсвойств (репозиториев) в одном месте (единица работы), особенно в контексте модульного тестирования, где вся инициализация базы данных и соединения не нужны или не нужны.
Крис Пратт
Потеря навигации по связанным сущностям - это плохо, и это противоречит ООП, но у вас будет больше контроля над тем, что запрашивается.
Алиреза
К моменту тестирования EF Core прошел долгий путь с готовыми In-Memory и In-Memory провайдерами Sqlite для обеспечения модульного тестирования. Добавьте docker, если вам нужно интеграционное тестирование для запуска тестов в контейнерной базе данных.
Судханшу Мишра
16

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

Девять хвостов
источник
Не могли бы вы рассказать о некоторых преимуществах для "Entity Framework дает вам множество кодов и функциональных преимуществ"?
ManirajSS
2
это то, что он имел в виду. var id = Entity.Where (i => i.Id == 1337) .Single () инкапсулирует и оборачивает это в репозиторий, который вы, в принципе, не можете сделать подобной логике запроса извне, что либо вынуждает вас добавить A к большему количеству кода хранилище и интерфейс для получения идентификатора. B возвращает контекст сущности из хранилища, так что вы можете написать логику запроса (что просто чепуха)
ColacX
14

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

Пересматривая шаблон репозитория

пышность
источник
Мне понравилась статья, но IMHO для корпоративных приложений, слой абстракции между DAL и Bl ДОЛЖЕН иметь функцию, поскольку вы не могли знать, что именно будет использоваться завтра. Но спасибо, что поделились ссылкой
1
Хотя лично я думаю, что это верно, например, для NHibernate ( ISessionFactoryи ISessionлегко поддается надругательству) DbContext, к сожалению , это не так просто с …
Patryk Ćwiek
6

Очень хорошая причина использовать шаблон репозитория - это позволить отделить вашу бизнес-логику и / или ваш пользовательский интерфейс от System.Data.Entity. В этом есть множество преимуществ, в том числе реальные преимущества в модульном тестировании, позволяющие ему использовать подделки или издевательства.

Джеймс Кулшоу
источник
Я согласен с этим ответом. Мои репозитории - это в основном просто методы расширения, которые ничего не делают, кроме построения деревьев выражений. За ОЧЕНЬ простую абстракцию, которая просто предоставляет общие функциональные возможности прямо поверх dbcontext. Единственная реальная цель абстракции - сделать IoC немного проще. Я думаю, что люди пытаются делать в своих репозиториях то, что им не следует делать. Они делают репо на объект или размещают там бизнес-логику, которая должна быть на уровне сервисов. Вам нужен только один простой репо. Это не обязательно, просто обеспечивает согласованный интерфейс.
Брэндон
Еще одна вещь, которую я просто хотел добавить. Да, CQRS - это значительно лучшая методология в большинстве случаев. Для некоторых клиентов, с которыми я работал, когда парни из базы данных плохо работают с разработчиками (что случается чаще, чем можно подумать, особенно в банках), EF поверх SQL - лучший вариант. В этом конкретном сценарии, когда у вас нет абсолютно никакого контроля над вашей базой данных, шаблон хранилища имеет смысл. Потому что очень напоминает структуру данных и легко переводит то, что происходит в базу данных, и наоборот. Это действительно политическое и логистическое решение, на мой взгляд. Чтобы успокоить Богов Богов.
Брэндон
1
Я фактически начинаю подвергать сомнению мои более ранние мнения по этому EF - это комбинированная модель «Единица работы» и «Хранилище». Как упомянул Крис Пратт выше с EF6, вы можете легко высмеивать объекты Context и DbSet. Я по-прежнему считаю, что доступ к данным должен быть заключен в классы, чтобы оградить классы бизнес-логики от фактического механизма доступа к данным, но пойти на все, и обернуть EF другим хранилищем, и абстракция Unit of Work кажется излишним.
Джеймс Кулшоу
Я не думаю, что это хороший ответ, потому что ваше заявление о поддержке состоит в том, что есть множество преимуществ, в то время как перечисление только одного. Тот, который вы делаете, не является хорошей причиной, потому что вы можете использовать базу данных в памяти для сущности, чтобы сделать модульное тестирование.
Джоэл МакБет,
@jcmcbeth, если вы посмотрите на мой комментарий прямо над вашим, вы увидите, что я изменил свое первоначальное мнение в отношении шаблона хранилища и EF.
Джеймс Кулшоу
0

У нас были проблемы с дублирующимися, но разными экземплярами Entity Framework DbContext, когда контейнер IoC, который new () обновлял репозитории для каждого типа (например, UserRepository и экземпляр GroupRepository, каждый из которых вызывает свой собственный IDbSet из DBContext), иногда может вызывать несколько контекстов на запрос (в контексте MVC / веб).

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

Муфаса
источник
Я сталкивался с этой проблемой в нескольких разных проектах.
ColacX
0

После опробования шаблона репозитория на небольшом проекте я настоятельно советую не использовать его; не потому, что это усложняет вашу систему, и не потому, что ложные данные - это кошмар, а потому, что ваше тестирование становится бесполезным !!

Пересматриваемые данные позволяют добавлять детали без заголовков, добавлять записи, которые нарушают ограничения базы данных, и удалять объекты, которые база данных откажется удалить. В реальном мире одно обновление может повлиять на несколько таблиц, журналов, истории, сводок и т. Д., А также на столбцы, такие как поле даты последнего изменения, автоматически сгенерированные ключи, вычисляемые поля.

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

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

Репозиторий был лучшей практикой в ​​те дни, когда мы использовали XBase, AdoX и Ado.Net, но с сущностью !! (Хранилище над хранилищем)

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

Завер Харун
источник
1
За исключением того, что вы НЕ хотите проверять поведение вашей базы данных в модульных тестах, поскольку это совсем не тот уровень тестирования.
Мариуш Джамро
Да, то, о чем вы говорите, это интеграционное тестирование, и оно действительно ценно, но модульные тесты совершенно разные. Ваши модульные тесты никогда не должны попадать в реальную базу данных, но вам рекомендуется добавить интеграционное тестирование, которое это делает.
Крис Пратт
-3

Это связано с миграциями. Невозможно заставить работать миграции, так как строка подключения находится в файле web.config. Но DbContext находится в слое Repository. IDbContextFactory должна иметь строку конфигурации для базы данных. Но миграция не может получить строку подключения из web.config.

Есть обходной путь, но я еще не нашел чистого решения для этого!

гром
источник