Шаблон репозитория: один класс репозитория для каждой сущности?

Допустим, у вас есть следующие сущности, определенные в классе LINQ:

Product
Customer
Category

Должен ли я иметь один класс репозитория для всех:

StoreRepository

... или я должен иметь:

ProductRepository
CustomerRepository
CategoryRepository

Каковы плюсы и минусы каждого из них? В моем случае у меня есть несколько «приложений» в моем решении ... Приложение Store - лишь одно из них.


person Derek Hunziker    schedule 19.08.2010    source источник


Ответы (4)


Вот моя точка зрения. Я строго придерживаюсь шаблона Repository. Должно быть 3 метода, которые принимают одну сущность. Добавить, Обновить, Удалить, общее определение.

public interface IRepository<T>
{
     void Add(T entity);
     void Update(T entity);
     void Delete(T entity);
}

Помимо этих методов, вы имеете дело с «запросом» или методом службы. Если бы я был на вашем месте, я бы сделал репозиторий таким, как указано выше, добавил бы «QueryProvider», как показано ниже, и поместил бы вашу бизнес-логику туда, где она принадлежит, либо в «Службы», либо в « Команды / запросы »(взято из CQRS, Google it).

public interface IQueryProvider<T>
{
     TResult Query<TResult>(Func<IQueryable<T>, TResult> query);
}

(Надеюсь, мое мнение окажется полезным :))

person zowens    schedule 19.08.2010
comment
Это действительно интересно, спасибо за подсказку. - person Michael D. Irizarry; 19.08.2010
comment
А как насчет Get () Entity? Куда это будет идти в репозитории? - person Praveen; 19.08.2010
comment
После этого Get () будет запросом с одним результатом. - person Necros; 20.08.2010
comment
@Necros, я согласен. Это, вероятно, можно было бы даже реализовать с помощью метода расширения, если вы так катаетесь. - person zowens; 20.08.2010
comment
@zowens лично мне нравятся мои репозитории немного богаче. Катаюсь вот так: github.com/Necroskillz/ NecroNetToolkit / tree / master / Source / - person Necros; 20.08.2010
comment
@Necros Я бы сказал, что такой подход не всегда лучше. Все методы, которые у вас есть в IRepository ‹T›, можно легко поместить в методы расширения. Кроме того, ваш IRepository ‹T› также обрабатывает запросы. Хотя я видел это много раз (даже так, как это описывает Мартин Фаулер), мне нравится избегать этого по той простой причине, что запрос - это не команда, это запрос. Дополнительным преимуществом является то, что если вы используете .NET 4.0, мой IRepository является противоположным вариантом, а мой IQueryProvider - ко-вариантом. (IRepository ‹в T› и IQueryProvider ‹вне T›) - person zowens; 20.08.2010
comment
На мой взгляд, наличие метода Query (q) усложняет тестирование. Почему бы не иметь IRepository<T> : IQueryable<T>. Это очень просто реализовать, если ваш базовый поставщик данных поддерживает абстракцию LINQ. Кроме того, я бы удалил Update (entity) и использовал транзакции (на уровне веб-запроса или метода обслуживания) с постоянством по достижимости для сохранения изменений. Конечно, вам следует обернуть свой репозиторий служебным слоем и провести там тестирование. Я бы никогда не открыл IQueryable ‹› на уровне MVC / контроллера. Кроме того, я согласен с Necros, что IRepo должен иметь метод Get (id). - person Ryan; 21.08.2010
comment
@Ryan Во-первых, важной концепцией является отделение запросов от функций репозитория. Репозиторий - это не уровень запросов, это объект, который управляет объектами в резервном хранилище, которое полностью игнорирует потребителей репозитория. Наследование от IQueryable - ужасный дизайн ... вам не нужно, чтобы экземпляры IQueryable плавали в памяти из-за таких вещей, как время жизни сеанса (например, в NH). ЭТО причина, по которой вы добавляете метод запроса, который принимает функцию, чтобы избежать утечки IQueryable. Как я уже сказал, get может быть расширением. Не требуется в интерфейсе IQueryProvider !!! - person zowens; 21.08.2010
comment
@Ryan Repositories не должны знать о транзакциях. Думайте ПРИНЦИП ЕДИНОЙ ОТВЕТСТВЕННОСТИ. Обновление в репозитории нормально. Вынуть обновление и поместить его в другое место было бы действительно глупо. Он ПРИНАДЛЕЖИТ репозиторию, и понятия транзакций НЕ принадлежат репозиториям, они принадлежат слоям сервисов. - person zowens; 21.08.2010
comment
@zowens Конечно, репозиторий не должен знать о транзакциях. Мои нет. Базовая реализация NHibernate / Linq To Sql делает это. Я обрабатываю транзакции с помощью модуля HTTP. Я не слежу за вашим аргументом об утечке IQueryables. Репозиторий и список в памяти должны быть неразличимы. Ответственность за понимание того, когда определенный запрос может вызвать загрузку слишком большого количества данных из базы данных, по-прежнему лежит на программисте. По моему опыту, написание отдельного объекта запроса - пустая трата времени без каких-либо дополнительных преимуществ. - person Ryan; 23.08.2010
comment
@Ryan Leaking IQueryables делает что-то вроде возврата из свойства или наследования репозитория от IQueryable. Когда вы отказываетесь от контроля над своим объектом IQueryable таким образом, вы позволяете потребителям вашего IRepository злоупотреблять API и получать доступ к запросу за пределами сеанса. Это приводит к значительному разрыву между тем, как следует использовать репозиторий, и тем, как он на самом деле будет работать, когда вы отказываетесь от части необработанных данных, таких как IQueryable. - person zowens; 24.08.2010
comment
@Ryan Репозиторий не находится в памяти, и его не следует рассматривать как единое целое. Конечно, репозиторий может действовать как абстракция поверх списка, но он не должен иметь всех гарантий, которые есть у списка. Например, поведение вне границ сеанса значительно отличается, когда есть фактические границы сеанса, в отличие от списка в памяти. Вы поднимаете хороший вопрос об ответственности за программирование. Лично я ограничиваю потребителей моих API тем, что мне нужно. Например, я не ВОЗВРАЩАЮ IQueryable, я разрешаю функции работать с выбранным мной IQueryable для RESTRICT. - person zowens; 24.08.2010
comment
@Ryan Вам также не хватает ключевого момента в моей аргументации, и вы еще не дали последовательного, логического объяснения, почему НЕ разделяют запрос и репозиторий. Я дам вам свое определение для обоих. Репозиторий выдает команду поддержке, игнорирующей настойчивость. Поставщик запроса является производителем проекции, принимая спецификацию запроса (Func ‹›). Серьезно, посмотрите CQRS. Существует ВЕСЬ список того, почему максимальное использование команд и запросов на любом уровне - это полный беспорядок. - person zowens; 24.08.2010
comment
@zowens: есть шанс показать пример реализации вашего IQueryProvider ‹T›. Довольно новичок в LINQ, поэтому еще не освоил функции Func ™. - person awrigley; 26.11.2010
comment
@zowens: какую пользу вы получаете от контравариантности IRepository<in T>? - person Nick Larsen; 03.12.2010
comment
@NickLarsen Это действительно не обязательно ... но если вы создаете подклассы, это может быть полезно. Например, у меня есть что-то под названием Widget и, возможно, ContentAreaWidget, который наследуется от виджета. Что ж, когда репо контравариантно, я могу назначить IRepository ‹Widget› на IRepository ‹ContentAreaWidget›. Но опять же, не обязательно. - person zowens; 05.01.2011

Все зависит от того, каким «доменным дизайном» вы собираетесь стать. Вы знаете, что такое агрегированный корень? В большинстве случаев стандартная типизированная команда with может выполнять все ваши базовые функции CRUD. Это начинает иметь значение только тогда, когда у вас появляются толстые модели с контекстом и границами.

person John Farrell    schedule 19.08.2010
comment
Мне пришлось его найти, но я думаю, что понимаю ... Итак, мой Продукт будет агрегированным, потому что он инкапсулирует Категории. И хотя у моего объекта Customer много продуктов, это отдельная идея и, следовательно, отдельный агрегат. Это звучит правильно? Вы предлагаете класс для каждого совокупного корня? - person Derek Hunziker; 19.08.2010
comment
Уоу, уоу, уоу, помедленнее. Поэтому, если вы попытаетесь реализовать DDD без полного понимания DDD, вы очень расстроитесь, и ваше качество пострадает. Пока не усложняйте задачу и просто создайте репозиторий для каждой сущности. - person John Farrell; 19.08.2010

Обычно для каждого совокупного корневого объекта будет один репозиторий. В книге DDD и агрегированном корневом объекте, а также о том, как нам разрабатывать классы репозитория. rel = "nofollow noreferrer"> ASP.NET MVC 2 в действии, посмотрите на него, если хотите узнать больше.

person Tien Do    schedule 21.08.2010

У меня был бы один репозиторий / объект, потому что всегда должна была быть карта из моего EntityTable в мой объект домена (например, в теле GetIQueryableCollection (). Как я смог написать этот повторяющийся код, сделав Шаблон T4, чтобы сгенерировать его для меня.

У меня есть пример проекта, который генерирует шаблон репозитория на codeplex http://t4tarantino.codeplex.com/ T4 Пример Toolbox Шаблоны для бизнес-классов и репозитория. Он может работать не так, как вам хотелось бы, без некоторых настроек, если вы уже не внедряете Tarintino и несколько других вкусностей, но шаблоны легко настроить.

using System;
using System.Collections.Generic;
using System.Linq;
using Cses.Core.Domain.Model;
using StructureMap;

namespace Cses.Core.Domain
{
    /// <summary>
    /// Core class for Schedule E
    /// </summary>
    public class ScheduleERepository : IScheduleERepository
    {

        private Cses.Core.Repository.SqlDataContext _context = new Cses.Core.Repository.SqlDataContext();

        /// <summary>
        /// constructor
        /// </summary>
        public ScheduleERepository() { }

        /// <summary>
        /// constructor for testing
        /// </summary>
        /// <param name="context"></param>
        public ScheduleERepository(Cses.Core.Repository.SqlDataContext context)
        {
            _context = context;

        }

        /// <summary>
        /// returns collection of scheduleE values
        /// </summary>
        /// <returns></returns>
        public IQueryable<ScheduleE> GetIQueryableCollection()
        {           
            return from entity in _context.ScheduleEs                  
               select new ScheduleE()
               {    
                    Amount = entity.Amount,
                    NumberOfChildren = entity.NumberChildren,
                    EffectiveDate = entity.EffectiveDate,
                    MonthlyIncome = entity.MonthlyIncome,
                    ModifiedDate = entity.ModifiedDate,
                    ModifiedBy = entity.ModifiedBy,                      
                    Id = entity.Id                          
               };           
        }
person James Fleming    schedule 24.08.2010
comment
Я не хочу показаться резким, но у меня много отзывов об этом коде. 1) Вы никогда не должны ссылаться на System.Web на уровне данных или домена. 2) Используйте контейнер IOC, такой как Windsor, для охвата одного контекста данных на веб-запрос (PerWebRequestLifestyle), чтобы вы могли воспользоваться преимуществами кеширования, пакетной обработки SQL, транзакций и т. Д. 3) Никогда не проглатывайте исключения. 4) Отделите ваши запросы от ваших команд (код, изменяющий БД). 5) Рефакторинг метода Compose 6) Принцип единой ответственности 7) Никогда не сбрасывайте строки. Where(x => x.MonthlyIncome.ToString() == key) хакерский. 8) Избегайте генерации кода. - person Ryan; 24.08.2010
comment
Спасибо за понимание, я сократил код до соответствующих частей, System.web был оплошностью. Получить ключ по строке - обязательный интерфейс при использовании Tarintino. Почему следует избегать генерации кода? Если это уменьшает избыточные усилия, обеспечивает согласованность и т. Д.? Не понимаю обратную сторону ... - person James Fleming; 25.08.2010