2008-12-02 3 views
19

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

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> 

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

SELECT 
    customer0_.ID AS ID8_2_, 
    customer0_.Name AS Name8_2_, 
    orders1_.CustomerID AS CustomerID__4_, 
    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 

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

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

Я использую все целые идентификаторы (PK), и я попытался реализовать IComparable из T и IEquatable из 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. Это необходимо для новых (переходных) сущностей, иначе они не будут иметь идентификатор изначально (моя модель предоставила их при сохранении).

+0

[Этот ответ] (http://stackoverflow.com/questions/5266180/fighting-cartesian-product-x-join-when-using-nhibernate-3-0-0/5285739#5285739) помог мне понять, как использовать запросы QueryOver и Future, чтобы охотно забирать детей и внуков, не возвращая дубликатов. Этот метод включает разбиение задачи на отдельные SQL-запросы, которые выполняются в одном обращении к базе данных. – 2011-10-20 20:29:56

ответ

21

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

.SetResultTransformer(new DistinctRootEntityResultTransformer()) 
+3

Я обнаружил, что это действительно решает проблему, * если * отображения изменены из пакета в набор, и я реализую необходимый IComparable в базовом классе. – 2009-01-07 21:52:50

5

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

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

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

Может быть, может помочь вам.

+0

Вы только что спасли мне несколько часов регрессионного тестирования из-за этого комментария. Весьма признателен. – 2009-09-02 18:45:07

+1

Это не будет работать над несколькими уровнями, как говорится в вопросах. Использование FetchMode.Eager для нескольких уровней приведет к декартовому продукту. Правильный SQL генерируется, но NHibernate не будет сортировать его для вас. – 2010-01-04 20:16:11

0

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

+0

@Sheraz - Надеюсь, вы ошибаетесь :-) НО, если вы правы, зачем он генерирует правильный SQL? Удача? – 2008-12-02 23:02:57

0

У меня была та же проблема. См. Это thread. Я не получил решения, но намек от Фабио. Используйте 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; 
} 
1

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

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].

Смежные вопросы