дизайн домена с помощью nhibernate

В моем домене есть что-то под названием Project, которое в основном содержит множество простых свойств конфигурации, описывающих, что должно произойти, когда проект получает executed. Когда проект выполняется, он производит огромное количество LogEntries. В моем приложении мне нужно проанализировать эти записи журнала для данного проекта, поэтому мне нужно иметь возможность частично последовательно загружать часть (временной интервал) записей журнала из базы данных (Oracle). Как бы вы смоделировали эту связь как таблицы БД и как объекты?

Я мог бы иметь таблицу Project и таблицу ProjectLog и иметь внешний ключ для первичного ключа Project и делать «то же самое» на уровне объекта, иметь класс Project и свойство

IEnumerable<LogEntry> LogEntries { get; }

и попросите NHibernate выполнить все сопоставления. Но как мне спроектировать свой ProjectRepository в этом случае? Я мог бы иметь методы

void FillLog(Project projectToFill, DateTime start, DateTime end);

Как я могу сказать NHibernate, что он не должен загружать LogEntries, пока кто-то не вызовет этот метод, и как мне заставить NHibernate загружать определенный период времени в рамках этого метода?

Я новичок в ORM, может быть, этот дизайн не оптимален для NHibernate или вообще? Может быть, я должен спроектировать его по-другому?


person bitbonk    schedule 19.06.2012    source источник


Ответы (2)


«Правильный» способ загрузки частичных/отфильтрованных списков/списков на основе критериев в NHibernate заключается в использовании запросов. Есть lazy="extra", но он не делает того, что вы хотите.

Как вы уже заметили, это нарушает модель DDD Root Aggregate -> Children. Я боролся только с этой проблемой в течение абсолютного возраста, потому что, прежде всего, я ненавидел то, что составляло постоянство проблем, загрязняющих мою модель предметной области, и я никогда не мог заставить поверхность API выглядеть «правильно». Методы фильтрации для класса сущностей-владельцев работают, но далеко не красивы.

В конце концов я решил расширить свой базовый класс сущностей (все мои сущности наследуются от него, что, как я знаю, немного не в моде в наши дни, но, по крайней мере, позволяет мне делать такие вещи последовательно) с помощью защищенного метода Query<T>(), который принимает Выражение LINQ, определяющее взаимосвязь, и внутри репозитория вызывает LINQ-to-NH и возвращает IQueryable<T>, который вы затем можете запрашивать по мере необходимости. Затем я могу замаскировать этот вызов под обычным свойством.

Базовый класс делает это:

protected virtual IQueryable<TCollection> Query<TCollection>(Expression<Func<TCollection, bool>> selector)
        where TCollection : class, IPersistent
    {
        return Repository.For<TCollection>().Where(selector);
    }

(Здесь я должен отметить, что моя реализация репозитория реализует IQueryable<T> напрямую, а затем делегирует работу NH Session.Query<T>())

И фасад работает так:

public virtual IQueryable<Form> Forms 
{ 
    get 
    { 
         return Query<Form>(x => x.Account == this); 
    } 
}

Это определяет связь списка между учетной записью и формой как обратную фактической сопоставленной связи (форма -> учетная запись).

Для «бесконечных» коллекций, где потенциально неограниченное количество объектов в наборе, это работает нормально, но это означает, что вы не можете отображать отношения непосредственно в NHibernate и, следовательно, не можете использовать свойство непосредственно в запросах NH, только косвенно.

Что нам действительно нужно, так это замена общих реализаций пакетов, списков и наборов NHibernate, которые знают, как использовать поставщика LINQ для прямого запроса к спискам. Один был предложен в качестве исправления (см. https://nhibernate.jira.com/browse/NH-2319). Как вы можете видеть, патч не был закончен или принят, и из того, что я вижу, предлагающий не переупаковывал его как расширение - Диего Миджелшон является пользователем здесь, на SO, так что, возможно, он присоединится... У меня есть протестировал предложенный им код в качестве POC, и он работает так, как рекламируется, но очевидно, что он не проверен, не гарантирован или не обязательно завершен, он может иметь побочные эффекты, и без разрешения на его использование или публикацию вы все равно не сможете его использовать.

До тех пор, пока команда NH не успеет написать/принять патч, который сделает это возможным, нам придется продолжать прибегать к обходным путям. У NH и DDD просто противоречивые взгляды на мир.

person Neil Hewitt    schedule 20.06.2012
comment
Итак, ваши объекты зависят от репозитория (или нескольких репозиториев, если на то пошло)? Это распространено в DDD? - person bitbonk; 20.06.2012
comment
Возможно нет. Я бы описал наш подход как вдохновленный DDD, а не рабское следование ему. В нашем сценарии в этом нет большого недостатка — если бы я был «чистым», то сделал бы это по-другому. Но у кого есть время на чистоту? :-) - person Neil Hewitt; 20.06.2012
comment
Ну, я думаю, по крайней мере, мне следует внедрить мой IRepository в мою сущность (предпочтительно ctor-injected). Вы делаете это с окружающим контекстом, который иногда может быть немного проблематичным. Чем в вашем случае обусловлено такое решение? - person bitbonk; 20.06.2012
comment
Раньше мы использовали Unity для IoC, и мне показалось, что это проблематично с NHibernate по ряду причин. В конце концов мы решили, что нас устраивают прямые зависимости — мы рады сообщить, что будем придерживаться NHibernate. Статический метод Repository.For‹T› «внедряет» экземпляры репозитория по требованию, а не во время ctor; раньше он разрешал IRepository‹T› с помощью Unity, чтобы разрешить макеты и т. д. Но для небольшого проекта это стало слишком накладным. - person Neil Hewitt; 20.06.2012
comment
На самом деле, это не совсем ответ на ваш вопрос. Я не считаю окружающий контекст проблематичным, и поскольку NH требует, чтобы все постоянные типы имели конструктор без параметров, ctor-injection отсутствует, если вы не добавите свою собственную фабрику для NH, что открывает совершенно другую банку червей. Так что мы просто обошли эту проблему. - person Neil Hewitt; 20.06.2012

Вместо того, чтобы иметь объект Project в качестве агрегатного корня, почему бы не переместить ссылку и позволить LogEntry имеют свойство Product, а также действуют как агрегированный корень.

public class LogEntry
{
    public virtual Product Product { get; set; }
    // ...other properties
}

public class Product
{
    // remove the LogEntries property from Product
    // public virtual IList<LogEntry> LogEntries { get; set; }
}

Теперь, поскольку обе эти сущности являются агрегированными корнями, у вас будет два разных репозитория: ProductRepository и LogEntryRepository. LogEntryRepository может иметь метод GetByProductAndTime:

IEnumerable<LogEntry> GetByProductAndTime(Project project, DateTime start, DateTime end);
person Miroslav Popovic    schedule 19.06.2012
comment
Немного странно делать простую запись в журнале и объединять корень или даже сущность. У него всего несколько простых свойств, таких как метка времени, сообщение, зарегистрированное значение, идентификатор проекта. У него нет дочерних объектов. Это больше похоже на тупой объект-значение. - person bitbonk; 19.06.2012
comment
Возможно, но это должен быть хороший компромисс против борьбы с поведением списка по умолчанию NHibernate. Я хотел бы услышать еще несколько мнений по этому поводу. Это интересная тема. - person Miroslav Popovic; 20.06.2012