Во время собеседования меня попросили объяснить, почему шаблон репозитория не подходит для работы с ORM, такими как Entity Framework. Почему это так?
Во время собеседования меня попросили объяснить, почему шаблон репозитория не подходит для работы с ORM, такими как Entity Framework. Почему это так?
Ответы:
Я не вижу причин для того, чтобы шаблон 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
}
}
Единственная лучшая причина не использовать шаблон хранилища с 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,
Может показаться, что это потенциальный аргумент для использования шаблона репозитория, но ключевое отличие здесь заключается в том, что сервис представляет собой более тонкий слой и ориентирован на возврат полностью запеченных данных, а не того, к чему вы продолжаете запрашивать, как при использовании репозиторий.
DbContext
EF6 + (см. Msdn.microsoft.com/en-us/data/dn314429.aspx ). Даже в меньших версиях, вы можете использовать поддельные DbContext
-like класс с издевался DbSet
с, так как DbSet
реализует iterface, IDbSet
.
Вот один из вариантов Ayende Rahien: « Архитектура в яме гибели»: зло уровня абстракции хранилища
Я еще не уверен, согласен ли я с его заключением. Это ловушка-22 - с одной стороны, если я оберну свой EF Context в репозитории для конкретного типа с помощью методов поиска данных для конкретного запроса, я на самом деле смогу выполнить модульное тестирование своего кода (своего рода), что практически невозможно с Entity Рамки одни. С другой стороны, я теряю способность выполнять богатые запросы и семантическое поддержание отношений (но даже когда у меня есть полный доступ к этим функциям, мне всегда кажется, что я хожу по скорлупе вокруг EF или любой другой ORM, которую я могу выбрать поскольку я никогда не знаю, какие методы может поддерживать или не поддерживать его реализация IQueryable, будет ли он интерпретировать мое добавление в коллекцию свойств навигации как создание или просто как ассоциацию, будет ли она загружаться лениво или активно или вообще не загружаться по умолчанию и т. д., так что, возможно, это к лучшему. Объектно-реляционное «отображение» с нулевым импедансом является чем-то вроде мифологического существа - возможно, именно поэтому последний выпуск Entity Framework был назван «Волшебный единорог»).
Тем не менее, получение ваших сущностей с помощью специфических для запроса методов извлечения данных означает, что ваши модульные тесты теперь в основном являются тестами белого ящика, и у вас нет выбора в этом вопросе, поскольку вы должны заранее точно знать, какой метод репозитория собирается тестируемая единица. позвоните, чтобы высмеять это. И вы все еще не тестируете сами запросы, если только вы не пишете интеграционные тесты.
Это сложные проблемы, которые требуют комплексного решения. Вы не можете исправить это, просто притворившись, что все ваши сущности являются отдельными типами без каких-либо отношений между ними, и каждый из них разбит на части в свой собственный репозиторий. Ну, вы можете , но это отстой.
Обновление: у меня был некоторый успех при использовании поставщика Effort для Entity Framework. Effort - это провайдер в памяти (с открытым исходным кодом), который позволяет вам использовать EF в тестах точно так же, как вы используете его для реальной базы данных. Я рассматриваю возможность переключения всех тестов в этом проекте, над которым я работаю, чтобы использовать этого провайдера, поскольку кажется, что все намного проще. Это единственное решение, которое я нашел до сих пор, которое решает все проблемы, о которых я говорил ранее. Единственное, что есть небольшая задержка при запуске моих тестов, поскольку это создает базу данных в памяти (для этого используется другой пакет под названием NMemory), но я не вижу в этом реальной проблемы. Есть статья Code Project, в которой говорится об использовании Effort (вместо SQL CE) для тестирования.
DbContext
. В DbSet
любом случае , вы всегда можете издеваться , и это в любом случае основа Entity Framework. DbContext
это немного больше, чем класс для размещения ваших DbSet
свойств (репозиториев) в одном месте (единица работы), особенно в контексте модульного тестирования, где вся инициализация базы данных и соединения не нужны или не нужны.
Причина, по которой вы, вероятно, сделали бы это, заключается в том, что это немного избыточно. Entity Framework дает вам множество преимуществ в кодировании и функциональности, поэтому вы используете его, если вы затем возьмете его и свернете в шаблон хранилища, отбрасывая эти преимущества, вы также можете использовать любой другой слой доступа к данным.
Теоретически, я думаю, что имеет смысл инкапсулировать логику соединения с базой данных, чтобы сделать ее более удобной для повторного использования, но, как показывает ссылка ниже, наши современные платформы по существу заботятся об этом сейчас.
ISessionFactory
и ISession
легко поддается надругательству) DbContext
, к сожалению , это не так просто с …
Очень хорошая причина использовать шаблон репозитория - это позволить отделить вашу бизнес-логику и / или ваш пользовательский интерфейс от System.Data.Entity. В этом есть множество преимуществ, в том числе реальные преимущества в модульном тестировании, позволяющие ему использовать подделки или издевательства.
У нас были проблемы с дублирующимися, но разными экземплярами Entity Framework DbContext, когда контейнер IoC, который new () обновлял репозитории для каждого типа (например, UserRepository и экземпляр GroupRepository, каждый из которых вызывает свой собственный IDbSet из DBContext), иногда может вызывать несколько контекстов на запрос (в контексте MVC / веб).
В большинстве случаев это все еще работает, но когда вы добавляете слой сервиса поверх этого, и эти сервисы предполагают, что объекты, созданные в одном контексте, будут правильно присоединены как дочерние коллекции к новому объекту в другом контексте, иногда происходит сбой, а иногда нет. т в зависимости от скорости коммитов.
После опробования шаблона репозитория на небольшом проекте я настоятельно советую не использовать его; не потому, что это усложняет вашу систему, и не потому, что ложные данные - это кошмар, а потому, что ваше тестирование становится бесполезным !!
Пересматриваемые данные позволяют добавлять детали без заголовков, добавлять записи, которые нарушают ограничения базы данных, и удалять объекты, которые база данных откажется удалить. В реальном мире одно обновление может повлиять на несколько таблиц, журналов, истории, сводок и т. Д., А также на столбцы, такие как поле даты последнего изменения, автоматически сгенерированные ключи, вычисляемые поля.
Вкратце, ваш тест на реальной базе данных дает реальные результаты, и вы можете тестировать не только свои сервисы и интерфейсы, но и поведение базы данных. Вы можете проверить, правильно ли ваши хранимые процедуры работают с данными, вернуть ожидаемый результат или действительно удалена удаленная вами запись! Такие тесты могут также выявить такие проблемы, как забвение выдачи ошибок из хранимых процедур и тысячи таких сценариев.
Я думаю, что Entity Framework реализует шаблон репозитория лучше, чем любая из статей, которые я читал до сих пор, и выходит далеко за рамки того, чего они пытаются достичь.
Репозиторий был лучшей практикой в те дни, когда мы использовали XBase, AdoX и Ado.Net, но с сущностью !! (Хранилище над хранилищем)
Наконец, я думаю, что слишком много людей тратят много времени на изучение и реализацию шаблона репозитория, и они отказываются его допустить. Главным образом, чтобы доказать себе, что они не тратили свое время.
Это связано с миграциями. Невозможно заставить работать миграции, так как строка подключения находится в файле web.config. Но DbContext находится в слое Repository. IDbContextFactory должна иметь строку конфигурации для базы данных. Но миграция не может получить строку подключения из web.config.
Есть обходной путь, но я еще не нашел чистого решения для этого!