NHibernate Eager Fetching Over Multi Levels

У меня есть трехуровневая иерархия сущностей: Customer-Order-Line, которую я хотел бы получить полностью для данного клиента, используя ISession.Get (id). У меня есть следующие фрагменты XML:

customer.hbm.xml:

<bag name="Orders" cascade="all-delete-orphan" inverse="false" fetch="join">
  <key column="CustomerID" />
  <one-to-many class="Order" />
</bag>

order.hbm.xml:

<bag name="Lines" cascade="all-delete-orphan" inverse="false" fetch="join">
  <key column="OrderID" />
  <one-to-many class="Line" />
</bag>

Я использовал атрибут fetch = "join", чтобы указать, что я хочу получить дочерние сущности для каждого родителя, и это построило правильный SQL:

SELECT 
    customer0_.ID AS ID8_2_, 
    customer0_.Name AS Name8_2_, 
    orders1_.CustomerID AS CustomerID_
public class BaseEntity : IComparable<BaseEntity>
{
    ...

    private Guid _internalID { get; set; }
    public virtual Guid ID { get; set; }

    public BaseEntity()
    {
        _internalID = Guid.NewGuid();
    }

    #region IComparable<BaseEntity> Members

    public int CompareTo( BaseEntity other )
    {
        if ( ID == Guid.Empty || other.ID == Guid.Empty )
            return _internalID.CompareTo( other._internalID );

        return ID.CompareTo( other.ID );
    }

    #endregion

    ...

 }
, orders1_.ID AS ID4_, orders1_.ID AS ID9_0_, orders1_.PostalAddress AS PostalAd2_9_0_, orders1_.OrderDate AS OrderDate9_0_, lines2_.OrderID AS OrderID__5_, lines2_.ID AS ID5_, lines2_.ID AS ID10_1_, lines2_.[LineNo] AS column2_10_1_, lines2_.Quantity AS Quantity10_1_, lines2_.ProductID AS ProductID10_1_ FROM Customer customer0_ LEFT JOIN [Order] orders1_ ON customer0_.ID=orders1_.CustomerID LEFT JOIN Line lines2_ ON orders1_.ID=lines2_.OrderID WHERE customer0_.ID=1

Пока все выглядит хорошо - SQL возвращает правильный набор записей (только с одним отдельным порядковым идентификатором), но когда я запускаю тест для подтверждения правильного количества сущностей (из NH) для заказов и строк, я получаю неверные результаты

Я должен получать (из моих тестовых данных) 1xOrder и 4xLine, однако я получаю 4xOrder и 4xLine. Похоже, что NH не распознает «повторяющуюся» группу информации о заказе в наборе результатов и не «повторно использует» объект «заказ».

Я использую все целочисленные идентификаторы (PK), и я попытался реализовать IComparable of T и IEquatable of T, используя этот идентификатор, в надежде, что NH увидит равенство этих сущностей. Я также пробовал переопределить Equals и GetHashCode для использования идентификатора. Ни одна из этих «попыток» не увенчалась успехом.

Поддерживается ли «многоуровневая выборка» для NH, и если да, требуется ли настройка XML (или какой-либо другой механизм) для ее поддержки?


NB: Я использовал решение sirocco с несколькими изменениями в моем собственном коде, чтобы наконец решить эту проблему. xml необходимо изменить с пакета на набор для всех коллекций, а сами права были изменены для реализации IComparable ‹>, что является требованием набора для обеспечения уникальности.

public class BaseEntity : IComparable<BaseEntity>
{
    ...

    private Guid _internalID { get; set; }
    public virtual Guid ID { get; set; }

    public BaseEntity()
    {
        _internalID = Guid.NewGuid();
    }

    #region IComparable<BaseEntity> Members

    public int CompareTo( BaseEntity other )
    {
        if ( ID == Guid.Empty || other.ID == Guid.Empty )
            return _internalID.CompareTo( other._internalID );

        return ID.CompareTo( other.ID );
    }

    #endregion

    ...

 }

Обратите внимание на использование поля InternalID. Это требуется для новых (переходных) сущностей, в противном случае у них не будет изначально идентификатора (в моей модели они предоставляются при сохранении).


person Ben Laan    schedule 02.12.2008    source источник
comment
Этот ответ помогло мне увидеть, как использовать запросы QueryOver и Future для быстрого получения дочерних и внуков без возврата дубликатов. Этот метод включает разбиение задачи на отдельные SQL-запросы, которые выполняются за одно обращение к базе данных.   -  person David McClelland    schedule 21.10.2011


Ответы (5)


Вы получаете 4XOrder и 4XLines, потому что соединение с линиями удваивает результаты. Вы можете установить трансформатор по критериям IC, например:

.SetResultTransformer(new DistinctRootEntityResultTransformer())
person sirrocco    schedule 15.12.2008
comment
Я обнаружил, что это действительно решает проблему, если сопоставления меняются от пакета к набору, и я реализую необходимый IComparable ‹T› в базовом классе. - person Ben Laan; 08.01.2009
comment
Интересно, что я использовал DistinctRoot .... но всегда с Bag и никогда не реализовывал IComparable. Но так и не пошла на 3-х уровневую загрузку :) - person sirrocco; 08.01.2009
comment
Кроме того, я думаю, что было бы лучше не загружать клиента, а затем переходить к Customer.Orders. Как правило, вы не просите покупателя рассказать вам о своих заказах. Поэтому было бы лучше иметь репо и: GetOrdersForCustomerId (int id). - person sirrocco; 08.01.2009
comment
Использование .SetResultTransformer(new DistinctRootEntityResultTransformer()) для двухуровневой активной загрузки - это нормально, более того, начинается создание декартовых соединений - person Rippo; 01.11.2010

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

session.CreateCriteria(typeof(Post))
    .SetFetchMode("Comments", FetchMode.Eager)
    .List();

В запросе критериев, чтобы избежать отложенной загрузки для одного конкретного запроса

Может быть, это поможет тебе.

person Tigraine    schedule 02.12.2008
comment
Вы только что сэкономили мне несколько часов на регрессионное тестирование из-за этого комментария. Весьма признателен. - person joshua.ewer; 02.09.2009
comment
Как говорится в вопросе, это не сработает на нескольких уровнях. Использование FetchMode.Eager для нескольких уровней приведет к декартовому произведению. Сгенерирован правильный SQL, но NHibernate не разберется с ним за вас. - person Samuel Meacham; 04.01.2010

Если вам нужно хранить свои индивидуальные запросы в виде пакетов, вы можете выполнить 2 запроса, каждый из которых имеет только 1 уровень иерархии. например, что-то вроде этого:

var temp = session.CreateCriteria( typeof( Order ) )
    .SetFetchMode( "Lines", NHibernate.FetchMode.Eager )
    .Add( Expression.Eq( "Customer.ID", id ) )
    .List();

var customer = session.CreateCriteria( typeof( Customer ) )
    .SetFetchMode( "Orders", NHibernate.FetchMode.Eager )
    .Add( Expression.Eq( "ID", id ) )
    .UniqueResult();

Строки загружаются в кэш NH в первом запросе, поэтому им не потребуется отложенная загрузка при последующем доступе, например, customer.Orders [0] .Lines [0].

person Mark Foreman    schedule 24.11.2011

@Tigraine: ваш запрос возвращает только сообщение с комментариями. Это приносит все сообщения со всеми комментариями (2 уровня). Бен просит клиента сделать заказ в LineItem (3 уровень). @Ben: насколько мне известно, nHibernate еще не поддерживает нетерпеливую загрузку до 3-го уровня. Hibernate поддерживает его.

person Sheraz    schedule 02.12.2008
comment
@Sheraz - Надеюсь, вы ошибаетесь :-) НО, если вы правы, почему он генерирует правильный SQL? Удача? - person Ben Laan; 03.12.2008

У меня была такая же проблема. См. Эту ветку. Я получил не решение, а подсказку от Фабио. Используйте Set вместо сумки. И это сработало.

Поэтому я предлагаю попробовать использовать set. Вам не обязательно использовать коллекцию Iesi, используйте IDictonary, и NH счастлив

public override IEnumerable<Baseline> GetAll()
{
     var baselines = Session.CreateQuery(@" from Baseline b
                                            left join fetch b.BaselineMilestones bm
                                            left join fetch bm.BaselineMilestonePrevious ")
                                            .SetResultTransformer(Transformers.DistinctRootEntity)
                                            .List<Baseline>();
     return baselines;
}
person Community    schedule 14.08.2009
comment
У меня тоже сработало переключение с ‹bag› на ‹set›, но трассировка SQL показывает, что декартово произведение все еще создается на сервере. Это означает, что NHibernate сортирует и фильтрует результаты на своем конце, чтобы правильно заполнить дочернюю и внушительную коллекции. Это, вероятно, не будет идеальным во многих ситуациях, так как это может означать, что тысячи или миллионы записей будут перетаскиваться по сети, а затем потребуется большая обработка для построения правильного графа объекта. - person Samuel Meacham; 05.01.2010