Загруженное дерево NHibernate попадает в базу данных при обходе Automapper

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

Вот мои файлы сопоставления Nhibernate:

Navigation.hbm.xml

<id name="ID" column="NavigationID">
  <generator class="identity"></generator>
</id>

<property name="IsDefault"/>
<property name="RoleType" column="RoleTypeID" />

<bag  name="Items" cascade="save-update" inverse="true" lazy="false" fetch="join">
  <key column="NavigationID"/>
  <one-to-many class="NavigationItem"/>
</bag>

NavigationItem.hbm.xml

 <class name="NavigationItem" table="NavigationItem">

<id name="ID" column="NavigationItemID">
  <generator class="identity"></generator>
</id>

<property name="ShowInMenu"/>
<property name="Order" column="[Order]" />

<many-to-one name="Page" column="PageID" lazy="false" fetch="join" />
<many-to-one name="Navigation" column="NavigationID" />
<many-to-one name="Parent" column="ParentNavigationItemID" />

<bag  name="Items" cascade="save-update" inverse="true">
  <key column="ParentNavigationItemID"/>
  <one-to-many class="NavigationItem"/>
</bag>

This is how I fill up a Navigation object:

ISession session = SessionProvider.Instance.CurrentSession;

            using (transaction = session.BeginTransaction())
            {
                var navigation = session.QueryOver<Navigation>()
                    .Where(x => x.IsDefault && x.RoleType == null)
                    .TransformUsing(new NHibernate.Transform.RootEntityResultTransformer())
                    .SingleOrDefault();

                transaction.Commit();
                return navigation;
            }

Поскольку для сумки Items объекта Navigation установлено значение lazy = "false", я получаю только один запрос к базе данных, чтобы получить объект Navigation, и левое соединение, чтобы также получить все элементы Navigation.

До сих пор все идеально.

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

Затем у меня есть модель пользовательского интерфейса, которую я отображаю с помощью Automapper.

Вот модели пользовательского интерфейса:

public class NavigationModel
{
    public List<NavigationItemModel> Items { get; set; }

    public NavigationModel()
    {
        Items = new List<NavigationItemModel>();
    }
}

public class NavigationItemModel
{
    public string PageName { get; set; }
    public string Url { get; set; }
    public bool Selected { get; set; }

    public NavigationItemModel Parent { get; set; }
    public List<NavigationItemModel> Items { get; set; }
}

И сопоставления автомата:

AutoMapper.Mapper
            .CreateMap<NavigationItem, NavigationItemModel>()
 // IF I REMOVE THE NEXT LINE, IT HITS THE DATABASE FOR EACH SUB-ITEM of the NavigationItem.Items
            .ForMember(m => m.Items, o => o.Ignore()); 

        AutoMapper.Mapper
            .CreateMap<Navigation, NavigationModel>();

Хорошо, теперь поведение такое:

  • Если я проигнорирую элемент NavigationItem.Items в сопоставлении, все будет хорошо, но отображаются только элементы Navigation и его элементы. Коллекции вложенных элементов элементов навигации не отображаются. НО база данных больше не поражается. Но я хочу, чтобы и другие элементы были нанесены на карту ...
  • Если я удалю строку под комментарием, база данных будет поражена для каждого из Navigation.Items, запрашивая его подпункты (где ParentID = Item.ID).

Есть идеи, что я делаю не так?

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


Я забыл включить генерируемый SQL:

Сначала такой запрос:

SELECT this_.NavigationID             as Navigati1
SELECT items0_.ParentNavigationItemID as ParentNa6_2_,
   items0_.NavigationItemID       as Navigati1_2_,
   items0_.NavigationItemID       as Navigati1_4_1_,
   items0_.ShowInMenu             as ShowInMenu4_1_,
   items0_.[Order]                as column3_4_1_,
   items0_.PageID                 as PageID4_1_,
   items0_.NavigationID           as Navigati5_4_1_,
   items0_.ParentNavigationItemID as ParentNa6_4_1_,
   page1_.PageID                  as PageID8_0_,
   page1_.Name                    as Name8_0_,
   page1_.Title                   as Title8_0_,
   page1_.Description             as Descript4_8_0_,
   page1_.URL                     as URL8_0_
FROM   NavigationItem items0_
   left outer join Page page1_
     on items0_.PageID = page1_.PageID
WHERE  items0_.ParentNavigationItemID = 1 /* @p0 */
2_, this_.IsDefault as IsDefault7_2_, this_.RoleTypeID as RoleTypeID7_2_, items2_.NavigationID as Navigati5_4_, items2_.NavigationItemID as Navigati1_4_, items2_.NavigationItemID as Navigati1_4_0_, items2_.ShowInMenu as ShowInMenu4_0_, items2_.[Order] as column3_4_0_, items2_.PageID as PageID4_0_, items2_.NavigationID as Navigati5_4_0_, items2_.ParentNavigationItemID as ParentNa6_4_0_, page3_.PageID as PageID8_1_, page3_.Name as Name8_1_, page3_.Title as Title8_1_, page3_.Description as Descript4_8_1_, page3_.URL as URL8_1_ FROM Navigation this_ left outer join NavigationItem items2_ on this_.NavigationID = items2_.NavigationID left outer join Page page3_ on items2_.PageID = page3_.PageID WHERE (this_.IsDefault = 1 /* @p0 */ and this_.RoleTypeID is null)

Затем, когда в игру вступает Automapper, создается список этих запросов, отличается только параметр p0 (от 1 до 12 ... количество элементов без родителей)

SELECT items0_.ParentNavigationItemID as ParentNa6_2_,
   items0_.NavigationItemID       as Navigati1_2_,
   items0_.NavigationItemID       as Navigati1_4_1_,
   items0_.ShowInMenu             as ShowInMenu4_1_,
   items0_.[Order]                as column3_4_1_,
   items0_.PageID                 as PageID4_1_,
   items0_.NavigationID           as Navigati5_4_1_,
   items0_.ParentNavigationItemID as ParentNa6_4_1_,
   page1_.PageID                  as PageID8_0_,
   page1_.Name                    as Name8_0_,
   page1_.Title                   as Title8_0_,
   page1_.Description             as Descript4_8_0_,
   page1_.URL                     as URL8_0_
FROM   NavigationItem items0_
   left outer join Page page1_
     on items0_.PageID = page1_.PageID
WHERE  items0_.ParentNavigationItemID = 1 /* @p0 */

Это взято из приложения NHProf, надеюсь, это поможет.

Спасибо, Космин


person noir    schedule 26.07.2011    source источник
comment
Привет, Космин, не могли бы вы опубликовать выполненный SQL-запрос?   -  person James Nail    schedule 26.07.2011
comment
Здравствуйте, Джеймс, я отредактировал вопрос с помощью сгенерированного SQL.   -  person noir    schedule 26.07.2011


Ответы (1)


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

Mapper.CreateMap<TSource, TDestination>().MaxDepth(2); // or 1, or 3, or whatever
person Vasea    schedule 26.07.2011
comment
Да, есть, но я хочу это иметь. Проблема в том, что я хочу, чтобы AutoMapper отображал их рекурсивно, но не попадал в базу данных. Вся информация там, загружена NHIbernate. Мой вопрос: почему NHibernate думает, что ему нужно попасть в базу данных, когда AutoMapper проходит через иерархию объектов? - person noir; 26.07.2011
comment
Значит, вызов navigation.Items [0] .Items [0] не запускает запрос к базе данных? - person Vasea; 26.07.2011
comment
Не уверен, но я не думаю, что каждая коллекция NavigationItem.Items сопоставлена ​​с вашим запросом. Возможно, указание размера партии на сумке может улучшить производительность за счет пакетной обработки запросов. В настоящее время я работаю с проектом NHibernate с множеством иерархических отношений в моих сущностях, и мне было очень сложно загрузить весь агрегированный корень со всеми коллекциями одним запросом без использования отдельной таблицы соединения многие-ко-многим, как в этом блоге. опубликовать ayende.com/blog/3107/ - person Vasea; 26.07.2011
comment
Нет, но я только что сделал еще один тест. Если я рекурсивно перебираю элементы и подэлементы, как я делал раньше, не будет обращения к базе данных. Однако, если в этом вызове я обращаюсь к свойству и что-то с ним делаю, например var a = item.ID *3, выполняется вызов базы данных. Тогда я пришел к выводу, что проблема не в AutoMapper, но почему NHibernate обращается к базе данных, когда я очень хочу загрузить коллекции элементов и подпунктов? Что-то не так с тем, как я указываю в сопоставлении, что это должно быть lazy=false и fetch="join"? Поскольку я вижу, что запрос получает все данные за один шаг ... - person noir; 26.07.2011
comment
Я предполагаю, что он не может загрузить все коллекции NavigationItem.Items - person Vasea; 26.07.2011
comment
Итак, суть в том, что Navigation.Items загружается. Из вашей статьи я нашел другую статью, которой тоже пользовался некоторое время назад. Я буду использовать левый / правый индекс для хранения элементов и только один уровень иерархии (элементы не будут иметь подпунктов). Итак, я загружу ВСЕ элементы навигации с их левым / правым индексами и перестрою дерево на клиенте. Эта статья: (sitepoint.com/hierarchical-data-database) Спасибо за помощь, это было действительно полезно! - person noir; 26.07.2011
comment
Все дело в ленивой загрузке nHibernate. Automapper пытается сопоставить все свойства (ссылки), поэтому через отражение он попадает в некоторые свойства объекта, у которого есть прокси. После этого действия nHibernate думает, что вам нужен этот объект или коллекции, и лениво загружает данные. - person zidane; 01.08.2011
comment
Да, это так, но проблема заключалась в том, что его модель была иерархической, поэтому AutoMapper рекурсивно загружал весь граф, поражая свойства, которые не были загружены. Несмотря на то, что он запрашивал все сущности, это не означает, что все отношения между ними были установлены. - person Vasea; 01.08.2011