Шаблон репозитория: реализация и отложенная загрузка модельных связей - PullRequest
10 голосов
/ 10 мая 2011

У меня есть приложение, которое занимается продуктами и категориями продуктов. Для каждого из них у меня есть модели, определенные с помощью POCO.

// Represents a product.
class Product {
  public virtual int ID { get; set; }
  public virtual string Name { get; set; }
  public virtual ProductCategory Category { get; set; }
}

// Represents a product category.
class ProductCategory {
  public virtual int ID { get; set; }
  public virtual string Name { get; set; }
  public virtual IEnumerable<Product> Products { get; set; }
}

Приложение использует репозиторий для доступа к этим моделям

// The interface implemented by the application's repository
interface IProductRepository {
  IEnumerable<Product> GetAllProducts();

  void Add(Product product);
  void Remove(Product product);
  void Save(Product product);
}

В классе Product свойство с именем Category типа ProductCategory должно загружаться только тогда, когда оно необходимо / доступно (отложенная загрузка). Я хочу, чтобы мои модели оставались POCO и содержали только структуру модели.

Правильный ли я подход?
Должен ли я иметь только идентификатор категории в классе Product и использовать отдельный репозиторий для категорий продуктов для загрузки категории?

Реализация отложенной загрузки и отношений
На данный момент моя реализация интерфейса репозитория возвращает объект типа, который расширяет тип Product и имеет поддержку отложенной загрузки через экземпляр репозитория.

Кто должен нести ответственность за загрузку категории продукта?

Меня интересует, как репозитории продуктов и категорий должны взаимодействовать для достижения отложенной загрузки? Должны ли они ссылаться друг на друга или у меня должен быть основной репозиторий с двумя суб-репозиториями и передать его в мои расширенные типы моделей?

Какой подход вы бы выбрали? (любые предложения и критика приветствуются)


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

Ответы [ 4 ]

5 голосов
/ 10 мая 2011

Несколько замечаний и моих мнений:

1)

Должен ли я иметь только идентификатор категории в классе Product и использовать отдельныйхранилище для категорий товаров для загрузки категории?

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

2)

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

Не уверен, что именно это означает.(Я хотел бы увидеть фрагмент кода, как вы это делаете.) Но я предполагаю, что в вашем производном Product классе вы как-то вставляете ссылку на хранилище, например:

public class ProductProxy : Product
{
    private IProductRepository _productRepo;

    public ProductProxy(IProductRepository productRepo)
    {
        _productRepo = productRepo;
    }

    // now you use _productRepo to lazily load something on request, do you?
}

Хорошо, очевидно, сейчас проблема загрузки категорий, поскольку IProductRepository не имеет методов для доступа к ним.

3)

Мне интереснокак должны взаимодействовать репозитории продуктов и категорий для достижения ленивой загрузки?Должны ли они ссылаться друг на друга или у меня должен быть основной репозиторий с двумя суб-репозиториями и передать его в мои расширенные типы моделей?

Ваш ProductRepository и CategoryRepository выглядят как экземпляры универсального репозитория, который отвечает толькодля одного типа сущности (в EF 4.1 это было бы похоже на DbSet<T>, где T равно Product или Category соответственно).

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

Я вижу два других варианта:

  • (в основном то, что вы уже упомянули)Наличие репозитория, который отвечает за Product и Category вместе.У вас все еще могут быть свои общие репозитории, но я бы больше рассматривал их как внутренние репозитории-помощники и использовал бы их только в качестве закрытых членов внутри основного репозитория.Таким образом, вы можете создать группу репозиториев, каждый из которых отвечает за несколько тесно связанных сущностей.

  • Введите Unit of Work, который может создать все ваши общие репозитории (сновав EF 4.1 это было бы что-то вроде заводского метода DbContext.Set<T>(), где DbContext - единица работы), а затем вставьте эту единицу работы в ваши производные экземпляры:

    public class ProductProxy : Product
    {
        private IUnitOfWork _unitOfWork;
    
        public ProductProxy(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }  
    
        public Category Category
        {
            get
            {
                // ...
                var productRepo = _unitOfWork.CreateGenericRepo<Product>();
                var categoryRepo = _unitOfWork.CreateGenericRepo<Category>();
                // you can pull out the repos you need and work with them
            }
            set { ... }
        }
    }
    

Я бы предпочел второй вариант, потому что в первом варианте вы можете оказаться в огромных репозиториях для поддержки загрузки всех возможных связей.Подумайте: у заказа есть элементы заказа, у элемента заказа есть продукт, у продукта есть категория, у клиента есть заказ, у клиента есть список адресов, у адреса есть список контактных лиц и т. Д. И т. Д. И т. Д. ...

4) (потому что вы также просили критиковать)

Вы пишете свой собственный ORM или вы пишете заявку?Ваш дизайн идет в направлении, которое может стать очень сложным, и вы, по моему мнению, изобретаете колесо заново.Если вы планируете использовать EF или NHibernate (или другие ORM), то вы создаете функции, которые уже доступны из коробки, вы только помещаете абстракции поверх них, которые не добавляют никакой ценности.Ленивая загрузка через динамические прокси происходит прозрачно в том смысле, что вы никогда не работаете явно с этими прокси в вашем коде, вы всегда работаете с вашими объектами POCO.Они невидимы и существуют только во время выполнения.Почему вы хотите разработать собственную инфраструктуру отложенной загрузки?

2 голосов
/ 10 мая 2011

NHibernate поддерживает отложенную загрузку через прокси-объекты (по умолчанию Castle DynamicProxy ), которые подклассируют классы, которыми манипулирует хранилище.NHibernate требует, чтобы вы пометили участников как virtual для поддержки этого сценария.Я не уверен относительно того, какой компонент инициирует вызов загрузки, когда это необходимо, но подозреваю, что это экземпляр прокси.

Лично я считаю, что маркировка элементов как virtual - это небольшая цена за ленивую загрузку

0 голосов
/ 10 мая 2011

Я хочу, чтобы мои модели оставались POCO и содержать только структуру модель.

Я бы спросил, почему форма данных важна для этих классов?

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

Я бы предположил, что данные, которые содержит объект, являются частными и должны быть инкапсулированы, и что поведение является тем, что должно быть в центре внимания объектов.

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

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

Попытка объединить все эти различные виды использования в один контракт или интерфейс приводит к, более или менее, объектам божественного объекта.

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

0 голосов
/ 10 мая 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...