Я прошел по многим путям и создал множество реализаций репозиториев для разных проектов и ... Я бросил полотенце и отказался от этого, вот почему.
Кодирование для исключения
Вы кодируете с вероятностью 1%, что ваша база данных изменится с одной технологии на другую? Если вы думаете о будущем состоянии вашего бизнеса и говорите, что да, это возможно, тогда а) у них должно быть много денег, чтобы позволить себе переход на другую технологию БД, или б) вы выбираете технологию БД для развлечения, или ) что-то пошло не так с первой технологией, которую вы решили использовать.
Зачем отказываться от богатого синтаксиса LINQ?
LINQ и EF были разработаны, чтобы с ними можно было делать изящные вещи для чтения и просмотра графов объектов. Создание и поддержка репозитория, который может дать вам такую же гибкость, - это чудовищная задача. По моему опыту, каждый раз, когда я создавал репозиторий, у меня ВСЕГДА была утечка бизнес-логики на уровень репозитория, чтобы либо сделать запросы более производительными, либо уменьшить количество обращений к базе данных.
Я не хочу создавать метод для каждой отдельной перестановки запроса, который мне нужно написать. Я мог бы также написать хранимые процедуры. Я не хочу GetOrder
, GetOrderWithOrderItem
, GetOrderWithOrderItemWithOrderActivity
, GetOrderByUserId
, и так далее ... Я просто хочу , чтобы получить основную сущность и траверс и включают в себя граф объектов , как я так пожалуйста.
Большинство примеров репозиториев - чушь собачья.
Если вы не разрабатываете что-то ДЕЙСТВИТЕЛЬНО простое, например блог или что-то еще, ваши запросы никогда не будут такими простыми, как 90% примеров, которые вы найдете в Интернете, связанных с шаблоном репозитория. Я не могу подчеркнуть это достаточно! Это то, что нужно ползти по грязи, чтобы понять. Всегда будет тот один запрос, который сломает ваш идеально продуманный репозиторий / решение, которое вы создали, и только тогда, когда вы сами догадаетесь, и начинается технический долг / эрозия.
Не проверяйте меня, братан
Но как насчет модульного тестирования, если у меня нет репозитория? Как я буду издеваться? Просто нет. Давайте посмотрим на это с двух сторон:
Нет репозитория - вы можете имитировать DbContext
использование IDbContext
или некоторые другие уловки, но тогда вы действительно тестируете LINQ to Objects, а не LINQ to Entities, потому что запрос определяется во время выполнения ... Хорошо, это не хорошо! Теперь дело за интеграционным тестом.
С репозиторием - теперь вы можете издеваться над своими репозиториями и проводить модульное тестирование промежуточных слоев. Отлично, правда? Ну, не совсем ... В приведенных выше случаях, когда вам нужно передать логику на уровень репозитория, чтобы сделать запросы более производительными и / или уменьшить количество обращений к базе данных, как ваши модульные тесты могут это покрыть? Теперь он находится на уровне репо, и вы не хотите тестировать, IQueryable<T>
верно? Также давайте будем честны, ваши модульные тесты не будут охватывать запросы, содержащие .Where()
предложение из 20 строк и.Include()
это связка отношений и снова обращается к базе данных, чтобы сделать все эти другие вещи, бла, бла, бла в любом случае, потому что запрос генерируется во время выполнения. Кроме того, поскольку вы создали репозиторий для игнорирования персистентности верхних уровней, если теперь вы хотите изменить технологию своей базы данных, извините, что ваши модульные тесты определенно не гарантируют те же результаты во время выполнения, возвращаясь к интеграционным тестам. Так что весь смысл репозитория кажется странным ...
2 цента
Мы уже теряем много функциональных возможностей и синтаксиса при использовании EF вместо простых хранимых процедур (массовые вставки, массовые удаления, CTE и т. Д.), Но я также кодирую на C #, поэтому мне не нужно вводить двоичный код. Мы используем EF, поэтому у нас есть возможность использовать разных поставщиков и работать с графами объектов в приятной взаимосвязанной форме среди многих вещей. Некоторые абстракции полезны, а некоторые нет.
Coding for the exception
: Использование репозиториев не позволяет переключать ядро базы данных. Речь идет об отделении бизнеса от настойчивости.DbSet
Является хранилищем , иDbContext
является единицей работы . Зачем внедрять шаблон репозитория, если ORM уже делает это за нас! Для тестирования просто смените провайдера наInMemory
. И сделай свои тесты! Это хорошо задокументировано в MSDN.Шаблон репозитория - это абстракция . Его цель - уменьшить сложность и сделать остальную часть кода неосведомленной. В качестве бонуса он позволяет писать модульные тесты вместо интеграционных .
Проблема в том, что многие разработчики не понимают назначение шаблонов и создают репозитории, которые передают информацию о сохраняемости вызывающей стороне (обычно путем раскрытия
IQueryable<T>
). Поступая так, они не получают выгоды от прямого использования OR / M.Обновите, чтобы адресовать другой ответ
Кодирование для исключения
Использование репозиториев не означает возможность переключения технологии сохранения (например, изменение базы данных или использование веб-сервиса и т. Д.). Речь идет об отделении бизнес-логики от постоянства, чтобы уменьшить сложность и взаимосвязь.
Модульные тесты против интеграционных тестов
Вы не пишете модульные тесты для репозиториев. период.
Но, вводя репозитории (или любой другой уровень абстракции между устойчивостью и бизнесом), вы можете писать модульные тесты для бизнес-логики. т.е. вам не нужно беспокоиться о том, что ваши тесты не пройдут из-за неправильно настроенной базы данных.
Что касается запросов. Если вы используете LINQ, вы также должны убедиться, что ваши запросы работают, как и в случае с репозиториями. и это делается с помощью интеграционных тестов.
Разница в том, что если вы не смешали свой бизнес с операторами LINQ, вы можете быть на 100% уверены, что сбой происходит именно ваш код сохранения, а не что-то еще.
Если вы проанализируете свои тесты, вы также увидите, что они намного чище, если вы не смешиваете проблемы (например, LINQ + Business logic).
Примеры репозитория
Большинство примеров - чушь собачья. Это очень верно. Однако, если вы погуглите какой-либо шаблон дизайна, вы найдете множество дрянных примеров. Это не причина избегать использования шаблона.
Создать правильную реализацию репозитория очень просто. На самом деле вам нужно следовать только одному правилу:
Не добавляйте ничего в класс репозитория до того момента, когда вам это понадобится.
Многие программисты ленивы и пытаются создать общий репозиторий и использовать базовый класс с множеством методов, которые могут им понадобиться. ЯГНИ. Вы пишете класс репозитория один раз и храните его, пока живет приложение (может быть, годы). Зачем облажаться, будучи ленивым. Держите его в чистоте без наследования базового класса. Это упростит чтение и поддержку.
(Приведенное выше утверждение является руководством, а не законом. Базовый класс вполне может быть мотивирован. Просто подумайте, прежде чем добавлять его, чтобы добавлять его по правильным причинам)
Старье
Вывод:
Если вы не против использования операторов LINQ в своем бизнес-коде и не заботитесь о модульных тестах, я не вижу причин не использовать Entity Framework напрямую.
Обновить
Я писал как о шаблоне репозитория, так и о том, что на самом деле означает «абстракция»: http://blog.gauffin.org/2013/01/repository-pattern-done-right/
Обновление 2
Опять же: объект с более чем 20 полями моделируется неправильно. Это БОЖЕСТВЕННАЯ сущность. Сломай.
Я не утверждаю, что
IQueryable
это не было сделано для вопросов. Я говорю, что это не подходит для уровня абстракции, такого как шаблон репозитория, поскольку он дырявый. Не существует 100% полного поставщика LINQ To Sql (например, EF).Все они имеют особенности реализации, например, как использовать нетерпеливую / ленивую загрузку или как выполнять операторы SQL «IN». Раскрытие
IQueryable
в репозитории заставляет пользователя знать все эти вещи. Таким образом, вся попытка абстрагироваться от источника данных окончилась неудачей. Вы просто увеличиваете сложность, не получая никакой выгоды от прямого использования OR / M.Либо правильно реализуйте шаблон репозитория, либо просто не используйте его.
(Если вы действительно хотите обрабатывать большие объекты, вы можете комбинировать шаблон репозитория с шаблоном спецификации . Это дает вам полную абстракцию, которую также можно проверить.)
источник
ИМО, и
Repository
абстракция, иUnitOfWork
абстракция занимают очень ценное место в любом значимом развитии. Люди будут спорить о деталях реализации, но, как есть много способов снять шкуру с кошки, есть много способов реализовать абстракцию.Ваш вопрос конкретно использовать или не использовать и почему.
Как вы, без сомнения, поняли, у вас уже есть оба этих шаблона, встроенные в Entity Framework
DbContext
: этоUnitOfWork
иDbSet
естьRepository
. Как правило, вам не нужно проводить модульное тестирование самихUnitOfWork
или,Repository
поскольку они просто упрощают взаимодействие между вашими классами и базовыми реализациями доступа к данным. Вам снова и снова придется имитировать эти две абстракции при модульном тестировании логики ваших сервисов.Вы можете имитировать, подделывать или что-то еще с внешними библиотеками, добавляя уровни зависимостей кода (которые вы не контролируете) между логикой, выполняющей тестирование, и логикой, которая тестируется.
Таким образом , точка незначительной, что имея собственную абстракцию для
UnitOfWork
иRepository
дает максимальный контроль и гибкость при насмешливый модульных тестов.Все очень хорошо, но для меня реальная сила этих абстракций состоит в том, что они предоставляют простой способ применения методов аспектно-ориентированного программирования и соблюдения принципов SOLID .
Итак, у вас есть
IRepository
:public interface IRepository<T> where T : class { T Add(T entity); void Delete(T entity); IQueryable<T> AsQueryable(); }
И его реализация:
public class Repository<T> : IRepository<T> where T : class { private readonly IDbSet<T> _dbSet; public Repository(PPContext context) { _dbSet = context.Set<T>(); } public T Add(T entity) { return _dbSet.Add(entity); } public void Delete(T entity) { _dbSet.Remove(entity); } public IQueryable<T> AsQueryable() { return _dbSet.AsQueryable(); } }
Пока ничего необычного, но теперь мы хотим добавить журналирование - легко с помощью декоратора журналирования .
public class RepositoryLoggerDecorator<T> : IRepository<T> where T : class { Logger logger = LogManager.GetCurrentClassLogger(); private readonly IRepository<T> _decorated; public RepositoryLoggerDecorator(IRepository<T> decorated) { _decorated = decorated; } public T Add(T entity) { logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() ); T added = _decorated.Add(entity); logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString()); return added; } public void Delete(T entity) { logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString()); _decorated.Delete(entity); logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString()); } public IQueryable<T> AsQueryable() { return _decorated.AsQueryable(); } }
Все сделано и без изменений существующего кода . Мы можем добавить множество других сквозных проблем, таких как обработка исключений, кэширование данных, проверка данных или что-то еще, и на протяжении всего нашего процесса проектирования и сборки самое ценное, что у нас есть, что позволяет нам добавлять простые функции без изменения какого-либо существующего кода. это наша
IRepository
абстракция .Я много раз встречал этот вопрос в StackOverflow - «как заставить Entity Framework работать в многопользовательской среде?».
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Если у вас есть
Repository
абстракция, ответ - «декоратор легко добавить».public class RepositoryTennantFilterDecorator<T> : IRepository<T> where T : class { //public for Unit Test example public readonly IRepository<T> _decorated; public RepositoryTennantFilterDecorator(IRepository<T> decorated) { _decorated = decorated; } public T Add(T entity) { return _decorated.Add(entity); } public void Delete(T entity) { _decorated.Delete(entity); } public IQueryable<T> AsQueryable() { return _decorated.AsQueryable().Where(o => true); } }
ИМО, вы всегда должны размещать простую абстракцию над любым сторонним компонентом, на который будут ссылаться более чем в нескольких местах. С этой точки зрения ORM - идеальный кандидат, поскольку на него есть ссылки во многих частях нашего кода.
Ответ, который обычно приходит в голову, когда кто-то говорит: «Зачем мне нужна абстракция (например
Repository
) над той или иной сторонней библиотекой?» - «А почему бы и нет?»PS Decorators чрезвычайно просто применять с помощью контейнера IoC, такого как SimpleInjector .
[TestFixture] public class IRepositoryTesting { [Test] public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository() { Container container = new Container(); container.RegisterLifetimeScope<PPContext>(); container.RegisterOpenGeneric( typeof(IRepository<>), typeof(Repository<>)); container.RegisterDecorator( typeof(IRepository<>), typeof(RepositoryLoggerDecorator<>)); container.RegisterDecorator( typeof(IRepository<>), typeof(RepositoryTennantFilterDecorator<>)); container.Verify(); using (container.BeginLifetimeScope()) { var result = container.GetInstance<IRepository<Image>>(); Assert.That( result, Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>))); Assert.That( (result as RepositoryTennantFilterDecorator<Image>)._decorated, Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>))); } } }
источник
Прежде всего, как предполагает некоторый ответ, EF сам по себе является шаблоном репозитория, нет необходимости создавать дополнительную абстракцию, просто чтобы назвать ее репозиторием.
Поддельный репозиторий для модульных тестов, действительно ли он нам нужен?
Мы позволяем EF взаимодействовать с тестовой БД в модульных тестах, чтобы протестировать нашу бизнес-логику прямо на тестовой БД SQL. Я не вижу никакой пользы в том, чтобы издеваться над каким-либо шаблоном репозитория. Что действительно не так при выполнении модульных тестов с тестовой базой данных? Поскольку это массовые операции невозможны, и мы пишем необработанный SQL. SQLite в памяти - идеальный кандидат для выполнения модульных тестов с реальной базой данных.
Ненужная абстракция
Вы хотите создать репозиторий только для того, чтобы в будущем вы могли легко заменить EF на NHbibernate и т. Д. Или что-то еще? Звучит отличный план, но действительно ли он рентабелен?
Linq убивает юнит-тесты?
Я хотел бы увидеть любые примеры того, как он может убивать.
Внедрение зависимостей, IoC
Ух ты, это отличные слова, конечно, они отлично смотрятся в теории, но иногда приходится выбирать между отличным дизайном и отличным решением. Мы все это использовали, и в итоге бросили все в мусор и выбрали другой подход. Размер против скорости (размер кода и скорость разработки) имеет огромное значение в реальной жизни. Пользователям нужна гибкость, им все равно, хорош ли ваш код с точки зрения DI или IoC.
Если вы не создаете Visual Studio
Все эти великолепные разработки необходимы, если вы создаете сложную программу, такую как Visual Studio или Eclipse, которая будет разрабатываться многими людьми и должна иметь широкие возможности настройки. Все великие паттерны разработки стали очевидны после многих лет разработки, через которые прошли эти IDE, и они развивались в месте, где все эти замечательные паттерны проектирования так важны. Но если вы занимаетесь простым веб-расчетом заработной платы или простым бизнес-приложением, лучше, чтобы вы со временем развивались в процессе разработки, вместо того, чтобы тратить время на его создание для миллионов пользователей, где оно будет развернуто только для сотен пользователей.
Репозиторий в виде отфильтрованного представления - ISecureRepository
С другой стороны, репозиторий должен быть отфильтрованным представлением EF, которое защищает доступ к данным, применяя необходимый заполнитель на основе текущего пользователя / роли.
Но это еще больше усложняет репозиторий, поскольку требует огромной базы кода. Люди в конечном итоге создают разные репозитории для разных типов пользователей или комбинации типов сущностей. Не только это, мы также получаем множество DTO.
Следующий ответ - это пример реализации фильтрованного репозитория без создания всего набора классов и методов. Он может не отвечать на вопрос напрямую, но может быть полезен при его выводе.
Отказ от ответственности: я являюсь автором Entity REST SDK.
http://entityrestsdk.codeplex.com
Имея в виду вышеизложенное, мы разработали SDK, который создает репозиторий фильтрованного представления на основе SecurityContext, который содержит фильтры для операций CRUD. И только два вида правил упрощают любые сложные операции. Первый - это доступ к сущности, а второй - это правило чтения / записи для свойства.
Преимущество состоит в том, что вы не переписываете бизнес-логику или репозитории для разных типов пользователей, вы просто блокируете или предоставляете им доступ.
public class DefaultSecurityContext : BaseSecurityContext { public static DefaultSecurityContext Instance = new DefaultSecurityContext(); // UserID for currently logged in User public static long UserID{ get{ return long.Parse( HttpContext.Current.User.Identity.Name ); } } public DefaultSecurityContext(){ } protected override void OnCreate(){ // User can access his own Account only var acc = CreateRules<Account>(); acc.SetRead( y => x=> x.AccountID == UserID ) ; acc.SetWrite( y => x=> x.AccountID == UserID ); // User can only modify AccountName and EmailAddress fields acc.SetProperties( SecurityRules.ReadWrite, x => x.AccountName, x => x.EmailAddress); // User can read AccountType field acc.SetProperties<Account>( SecurityRules.Read, x => x.AccountType); // User can access his own Orders only var order = CreateRules<Order>(); order.SetRead( y => x => x.CustomerID == UserID ); // User can modify Order only if OrderStatus is not complete order.SetWrite( y => x => x.CustomerID == UserID && x.OrderStatus != "Complete" ); // User can only modify OrderNotes and OrderStatus order.SetProperties( SecurityRules.ReadWrite, x => x.OrderNotes, x => x.OrderStatus ); // User can not delete orders order.SetDelete(order.NotSupportedRule); } }
Эти правила LINQ сравниваются с базой данных в методе SaveChanges для каждой операции, и эти правила действуют как брандмауэр перед базой данных.
источник
Существует много споров о том, какой метод правильный, поэтому я смотрю на него как на приемлемый, поэтому я использую тот, который мне нравится больше всего (который не является репозиторием, UoW).
В EF UoW реализуется через DbContext, а DbSets - это репозитории.
Что касается того, как работать с уровнем данных, я просто работаю непосредственно с объектом DbContext, для сложных запросов я создам методы расширения для запроса, которые можно использовать повторно.
Я считаю, что у Айенде также есть несколько сообщений о том, насколько плохо абстрагироваться от операций CUD.
Я всегда создаю интерфейс, и мой контекст наследуется от него, поэтому я могу использовать контейнер IoC для DI.
источник
Что больше всего применимо к EF, так это не шаблон репозитория. Это шаблон фасада (абстрагирование вызовов методов EF в более простые и удобные версии).
EF - это тот, кто применяет шаблон репозитория (а также шаблон единицы работы). То есть EF абстрагируется от уровня доступа к данным, так что пользователь понятия не имеет, что он имеет дело с SQLServer.
И при этом, большинство «репозиториев» в EF даже не являются хорошими фасадами, поскольку они просто сопоставляются с отдельными методами в EF даже до такой степени, что имеют одинаковые подписи.
Таким образом, две причины для применения этого так называемого шаблона «репозиторий» над EF заключаются в том, чтобы упростить тестирование и установить к нему подмножество «стандартных» вызовов. Неплохо само по себе, но явно не репозиторий.
источник
Linq в настоящее время является «репозиторием».
ISession + Linq уже является репозиторием, и вам не нужны ни
GetXByY
методы, ниQueryData(Query q)
обобщения. Я немного параноидально отношусь к использованию DAL, но все же предпочитаю интерфейс репозитория. (С точки зрения ремонтопригодности нам все еще нужно иметь фасад над конкретными интерфейсами доступа к данным).Вот репозиторий, который мы используем - он отделяет нас от прямого использования nhibernate, но предоставляет интерфейс linq (как доступ к ISession в исключительных случаях, которые в конечном итоге подлежат рефакторингу).
class Repo { ISession _session; //via ioc IQueryable<T> Query() { return _session.Query<T>(); } }
источник
Repository (или однако один хочет называть его) в это время для меня в основном о абстрагируясь от персистенции слоя.
Я использую его вместе с объектами запросов, поэтому у меня нет связи с какой-либо конкретной технологией в моих приложениях. А также это значительно облегчает тестирование.
Итак, я склонен
public interface IRepository : IDisposable { void Save<TEntity>(TEntity entity); void SaveList<TEntity>(IEnumerable<TEntity> entities); void Delete<TEntity>(TEntity entity); void DeleteList<TEntity>(IEnumerable<TEntity> entities); IList<TEntity> GetAll<TEntity>() where TEntity : class; int GetCount<TEntity>() where TEntity : class; void StartConversation(); void EndConversation(); //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository. TResult ExecuteQuery<TResult>(IQueryObject<TResult> query); }
Возможно добавление асинхронных методов с обратными вызовами в качестве делегатов. Репо легко реализовать в целом , поэтому я могу не касаться строки реализации от приложения к приложению. Что ж, это правда, по крайней мере, при использовании NH, я сделал то же самое с EF, но заставил меня возненавидеть EF. 4. Разговор - это начало транзакции. Очень здорово, если несколько классов используют один экземпляр репозитория. Кроме того, для NH одно репо в моей реализации равно одной сессии, которая открывается по первому запросу.
Затем объекты запроса
public interface IQueryObject<TResult> { /// <summary>Provides configuration options.</summary> /// <remarks> /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository. /// If not used through a repository, it can be useful as a configuration option. /// </remarks> void Configure(object parameter); /// <summary>Implementation of the query.</summary> TResult GetResult(); }
Для конфигурации я использую в NH только для перехода в ISession. В EF смысла более-менее нет.
Пример запроса: .. (NH)
public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>> where TEntity : class { public override IList<TEntity> GetResult() { return this.Session.CreateCriteria<TEntity>().List<TEntity>(); } }
Для выполнения запроса EF вам потребуется контекст в абстрактной базе, а не сеанс. Но, конечно, ifc будет таким же.
Таким образом, сами запросы инкапсулируются и легко тестируются. Лучше всего то, что мой код полагается только на интерфейсы. Все очень чисто. Объекты домена (бизнес) - это всего лишь объекты, например, нет смешивания обязанностей, например, при использовании шаблона активной записи, который трудно тестировать и смешивает код доступа к данным (запроса) в объекте домена и при этом смешивает проблемы (объект, который выбирает сам??). Все по-прежнему могут создавать POCO для передачи данных.
В общем, этот подход обеспечивает многократное использование кода и простоту, при этом я ничего не могу себе представить. Любые идеи?
И большое спасибо Айенде за его отличные посты и постоянную преданность делу. Здесь его идеи (объект запроса), а не мои.
источник
Для меня это простое решение с относительно небольшим количеством факторов. Факторы:
Итак, если мое приложение не может оправдать № 2, разделить модели домена и данных, то я обычно не буду беспокоиться о № 5.
источник