Подтвердить что ты не робот

Лучшая практика шаблонов репозитория

Итак, я реализую шаблон репозитория в приложении и сталкивался с двумя "проблемами" в моем понимании шаблона:

  • Querying - я прочитал ответы, которые IQueryable не следует использовать при использовании репозиториев. Тем не менее, очевидно, что вы хотите, чтобы вы не возвращали полный список объектов каждый раз, когда вы вызываете метод. Должно ли быть реализовано? Если у меня есть метод IEnumerable, называемый List, какой общий "лучший способ" для IQueryable? Какие параметры должны/не должны иметь?

  • Скалярные значения. Каким образом (используя шаблон репозитория) можно вернуть одно скалярное значение без необходимости возвращать всю запись? С точки зрения производительности, было бы более эффективным возвращать только одно скалярное значение по всей строке?

4b9b3361

Ответ 1

Строго говоря, репозиторий предлагает семантику коллекции для получения/размещения объектов домена. Он обеспечивает абстракцию вокруг вашей реализации материализации (ORM, ручная прокатка, макет), чтобы потребители объектов домена были отделены от этих деталей. На практике репозиторий обычно абстрагирует доступ к объектам, то есть к объектам домена с идентификатором и обычно к постоянному жизненному циклу (в аромате DDD репозиторий обеспечивает доступ к совокупным корням).

Минимальный интерфейс для репозитория выглядит следующим образом:

void Add(T entity);
void Remove(T entity);
T GetById(object id);
IEnumerable<T> Find(Specification spec);

Хотя вы увидите различия в именах и добавление семантики Save/SaveOrUpdate, вышесказанное является "чистой" идеей. Вы получаете элементы добавления/удаления ICollection и некоторые искатели. Если вы не используете IQueryable, вы также увидите методы поиска в репозитории, например:

FindCustomersHavingOrders();
FindCustomersHavingPremiumStatus();

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

Кроме того, использование IQueryable "прерывает" шаблон: репозиторий с IQueryable может предоставлять или не предоставлять доступ к "объектам домена". IQueryable предоставляет клиенту множество опций о том, что будет реализовано при окончательном выполнении запроса. Это основная идея дискуссии об использовании IQueryable.

Что касается скалярных значений, вы не должны использовать репозиторий для возврата скалярных значений. Если вам нужен скаляр, вы обычно получаете это от самого объекта. Если это звучит неэффективно, это возможно, но вы можете не заметить, в зависимости от ваших характеристик нагрузки/требований. В случаях, когда вам нужны альтернативные виды объекта домена, из-за причин производительности или из-за необходимости объединять данные со многих объектов домена, у вас есть два варианта.

1) Используйте репозиторий объектов, чтобы найти указанные объекты и проект/карту в сплющенном виде.

2) Создайте интерфейс поиска, предназначенный для возврата нового типа домена, который инкапсулирует сглаженное представление, которое вам нужно. Это не будет репозиторий, потому что семантики коллекции не будет, но она может использовать существующие репозитории под обложками.

Одна вещь, которую следует учитывать, если вы используете "чистый" репозиторий для доступа к постоянным объектам, заключается в том, что вы нарушаете некоторые преимущества ORM. В "чистой" реализации клиент не может предоставить контекст того, как будет использоваться объект домена, поэтому вы не можете указать репозиторий: "Эй, я просто собираюсь изменить свойство customer.Name, так что don" Не волнуйся, чтобы получить эти загруженные ссылки". С другой стороны, вопрос заключается в том, должен ли клиент знать об этом. Это обоюдоострый меч.

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

Add(T entity);
Remove(T entity);
T GetById(object id);
IQueryable<T> Find();

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

Ответ 2

В ответ на @lordinateur мне не очень нравится метод defacto для указания интерфейса репозитория.

Поскольку для интерфейса в вашем решении требуется, чтобы каждая реализация репозитория требовала, по крайней мере, Add, Remove, GetById и т.д. Теперь рассмотрим сценарий, когда нет смысла сохранять через конкретный экземпляр репозитория, вы все равно должны реализовать оставшиеся методы с NotImplementedException или что-то в этом роде.

Я предпочитаю разделять объявления интерфейса репозитория следующим образом:

interface ICanAdd<T>
{
    T Add(T entity);
}

interface ICanRemove<T>
{
    bool Remove(T entity);
}

interface ICanGetById<T>
{
    T Get(int id);
}

Реализация конкретного репозитория для объекта SomeClass может выглядеть следующим образом:

interface ISomeRepository
    : ICanAdd<SomeClass>, 
      ICanRemove<SomeClass>
{
    SomeClass Add(SomeClass entity);
    bool Remove(SomeClass entity);
}

Давайте сделаем шаг назад и посмотрим, почему я считаю, что это лучше, чем реализация всех методов CRUD в одном общем интерфейсе.

Некоторые объекты имеют разные чем другие. Клиент объект не может быть удален, PurchaseOrder не может быть обновлен, и Объект ShoppingCart может быть только создано. Когда вы используете общий Интерфейс IRepository очевидно, вызывает проблемы в реализация.

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

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

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

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

Ответ 3

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

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

Относительно 2: Естественно, более эффективно использовать только одно значение из базы данных, чем вся запись. Но используя шаблон репозитория, вы вообще не будете возвращать записи, вы будете возвращать бизнес-объекты. Таким образом, логика приложения не должна занимать поля, но с объектами домена.

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

Гораздо важнее иметь чистый, понятный код - вместо микроскопической оптимизации производительности.