Специальные данные и шаблон репозитория

Каков рекомендуемый способ возврата специальных (настраиваемых в каждом конкретном случае) данных из репозитория, которые не соответствуют каким-либо объектам модели или расширяют некоторые из них?

Примером 101 может быть вездесущее приложение hello word: система блогов. Предположим, вы хотите загрузить список сообщений, где запись сообщения содержит некоторую дополнительную информацию, которой нет в сущности сообщения. Допустим, это количество комментариев, а также дата и время последнего комментария. Это было бы очень тривиально, если бы кто-то использовал старый добрый SQL и считывал данные непосредственно из базы данных. Как я должен сделать это оптимально, используя шаблон репозитория, если я не могу позволить себе загружать все коллекции комментариев для каждого поста, и я хочу сделать это одним обращением к базе данных? Есть ли какой-нибудь широко используемый шаблон для этой ситуации? Теперь представьте, что у вас есть умеренно сложное веб-приложение, в котором для каждой страницы требуются немного разные пользовательские данные, а загрузка полных иерархий невозможна (производительность, требования к памяти и т. д.).

Несколько случайных идей:

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

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

  3. Используйте LINQ, создавайте специальные запросы и читайте анонимные классы.

Примечание. Я попросил аналогичный вопрос недавно, но он показался слишком общим и не привлек большого внимания.

Пример:

Основываясь на предложениях в ответах ниже, я добавляю более конкретный пример. Вот ситуацию, которую я пытался описать:

IEnumarable<Post> posts = repository.GetPostsByPage(1);
foreach (Post post in posts)
{

    // snip: push post title, content, etc. to view

    // determine the post count and latest comment date
    int commentCount = post.Comments.Count();
    DateTime newestCommentDate = post.Comments.Max(c => c.Date);

    // snip: push the count and date to view

}

Если я не сделаю ничего лишнего и воспользуюсь готовым ORM, это приведет к n+1 запросам или, возможно, к одному запросу, загружающему все сообщения и комментарии. Но в идеале я хотел бы иметь возможность просто выполнить один SQL, который будет возвращать одну строку для каждого сообщения, включая заголовок сообщения, тело и т. д., а также количество комментариев и дату последнего комментария в одном и том же. Это тривиально в SQL. Проблема в том, что мой репозиторий не сможет прочитать и поместить этот тип данных в модель. Куда идут максимальные даты и количество?

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


person Jan Zich    schedule 07.03.2009    source источник


Ответы (5)


В этом вопросе многое. Вам нужны эти конкретные данные для процедуры отчетности? Если это так, то правильное решение состоит в том, чтобы иметь отдельный доступ к данным для целей отчетности. Сглаженные базы данных, представления и т. д.

Или это специальный запрос? Если да, то у Ayende есть пост по этой самой проблеме. http://ayende.com/Blog/archive/2006/12/07/ComplexSearchingQueryingWithNHibernate.aspx

Он использует объект «Искатель». Он использует NHibernate, поэтому, по сути, он создает отдельный запрос.

Я делал что-то подобное в прошлом, создавая объект Query, который я могу заполнить перед передачей его в репозиторий (некоторые сторонники DDD будут возражать против этого, но я нахожу его элегантным и простым в использовании).

Объект Query реализует гибкий интерфейс, поэтому я могу написать это и получить результаты обратно:

IQuery query = new PostQuery()
   .WithPostId(postId)
   .And()
   .WithCommentCount()
   .And()
   .WithCommentsHavingDateLessThan(selectedDate);


Post post = _repository.Find(query);

Однако в вашем конкретном случае я должен удивиться вашему дизайну. Вы говорите, что не можете загрузить комментарии к посту. Почему? Вы просто слишком беспокоитесь о производительности? Является ли это случаем преждевременной оптимизации? (мне кажется)

Если бы у меня был объект Post, это был бы мой совокупный корень, и к нему прилагались бы комментарии. И тогда все, что вы хотите сделать, будет работать в каждом сценарии.

person Chris Holmes    schedule 07.03.2009
comment
Спасибо. Ваши предложения кажутся хорошим началом. Интересно, а где вы на самом деле храните количество комментариев? Разумеется, для него в сущности Post нет отдельного элемента данных. - person Jan Zich; 07.03.2009
comment
Что касается производительности, пример с сообщениями в блоге был просто примером. Реальное приложение, которое я имею в виду, уже запущено, и мы не можем позволить себе загружать все коллекции. - person Jan Zich; 07.03.2009
comment
Настоящая проблемная область имеет значение. Никто не может сказать, есть ли у вас ошибка в дизайне вашего домена, что на самом деле может привести к лучшему решению. Сообщения и комментарии — это решение проблемы, и ваши вопросы не имеют контекстуального смысла. - person Chris Holmes; 08.03.2009

Поскольку нам нужно было срочно решить проблему, которую я изложил в своем первоначальном вопросе, мы прибегли к следующему решению. Мы добавили набор свойств (словарь) к каждому объекту модели, и, если это необходимо, DAL вставляет в него пользовательские данные. Чтобы установить какой-то контроль, коллекция свойств управляется экземплярами назначенного класса и поддерживает только простые типы данных (целые числа, даты, ...), что все, что нам нужно при перемещении, и, скорее всего, когда-либо понадобится . Типичный случай, который это решает: загрузка сущности с подсчетами для ее вложенных коллекций вместо полных заполненных коллекций. Я подозреваю, что это, вероятно, не получает никакой награды за разработку программного обеспечения, но это было самое простое и практичное решение для нашего случая.

person Jan Zich    schedule 15.03.2009
comment
Я думаю, что другим вариантом было бы прикрепление некоторых именованных запросов к объектам и использование их в репозиториях. Тем не менее, интересный вопрос, жаль, что так мало людей, кажется, вникали в него. - person wds; 10.06.2009

Не могу сказать, что я действительно понимаю, в чем проблема, просто стреляю в воздух:

  • Добавьте конкретную сущность, чтобы инкапсулировать нужную информацию.
  • Добавить свойство Комментарии к сообщению. (Я не понимаю, почему это потребует от вас получения всех комментариев — вы можете просто получить комментарии для конкретного сообщения, которое вы загружаете)
  • Используйте ленивую загрузку, чтобы получать комментарии только при доступе к свойству.

Я думаю, у вас было бы больше шансов увидеть ответ на свой вопрос, если бы вы сделали платформу, язык и сопоставитель O/R специфичными (кажется, .NET C# или VB, поскольку вы упомянули LINQ. LINQ 2 SQL? Entity framework? Что-то еще ?)

person Kurt Schelfthout    schedule 07.03.2009
comment
Спасибо, что указали мне на это. Я добавил простой конкретный пример и дополнительные пояснения. - person Jan Zich; 07.03.2009

Если вы не привязаны к RDBM, то вам стоит обратить внимание на такие базы данных, как CouchDB или Amazons SimpleDB. То, что вы описываете, тривиально в представлении CouchDB. Это, вероятно, на самом деле не отвечает на ваш конкретный вопрос, но иногда полезно взглянуть на радикально разные варианты.

person Jeremy Wall    schedule 01.04.2009

Для этого у меня обычно есть RepositoryStatus и класс Status, который действует как мой объект передачи данных (DTO). Класс Status используется на моем уровне службы приложений (по той же причине), от которого наследуется RepositoryStatus. Затем с помощью этого класса я могу возвращать сообщения об ошибках, объекты ответа и т. д. из слоя репозитория. Этот класс является универсальным в том смысле, что он принимает любой объект и передает его получателю.

Вот класс состояния:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RanchBuddy.Core.Domain;
using StructureMap;

namespace RanchBuddy.Core.Services.Impl
{
    [Pluggable("Default")]
    public class Status : IStatus
    {
        public Status()
        {
            _messages = new List<string>();
            _violations = new List<RuleViolation>();
        }

        public enum StatusTypes
        {
            Success,
            Failure
        }

        private object _object;
        public T GetObject<T>()
        {
            return (T)_object;
        }
        public void SetObject<T>(T Object)
        {
            _object = Object;
        }

        private List<string> _messages;
        public void AddMessage(string Message)
        {
            _messages.Add(Message);
        }
        public List<string> GetMessages()
        {
            return _messages;
        }
        public void AddMessages(List<string> Messages)
        {
            _messages.AddRange(Messages);
        }

        private List<RuleViolation> _violations;
        public void AddRuleViolation(RuleViolation violation)
        {
            _violations.Add(violation);
        }
        public void AddRuleViolations(List<RuleViolation> violations)
        {
            _violations.AddRange(violations);
        }
        public List<RuleViolation> GetRuleViolations()
        {
            return _violations;
        }
        public StatusTypes StatusType { get; set; }
    }
}

А вот RepositoryStatus:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RanchBuddy.Core.Services.Impl;
using StructureMap;

namespace RanchBuddy.Core.DataAccess.Impl
{
    [Pluggable("DefaultRepositoryStatus")]
    public class RepositoryStatus : Status, IRepositoryStatus
    {

    }
}

Как видите, RepositoryStatus еще не делает ничего особенного и просто полагается на утилиты объектов Status. Но я хотел оставить за собой право продления на более поздний срок!

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

person Andrew Siemer    schedule 13.06.2009