2010-11-04 2 views
1

У меня есть сущность и плавное отображение, которое выглядит так.Проблема с NHibernate N + 1

public class Client : EntityWithTypedId<long> 
{    
    [Length(Max=50)] 
    public virtual string GivenName { get; set; } 

    public virtual IList<Address> Addresses { get; set; } 
} 

public class ClientMap : ClassMap<Client> 
{  
    public ClientMap() 
    { 
     Schema("dbo"); 
     Table("Client");    
     Id(x => x.Id, "ClientId").GeneratedBy.Identity();   
     Map(x => x.GivenName, "GivenName");    
     HasManyToMany(x => x.Addresses) 
      .FetchType.Join() 
      .Cascade.AllDeleteOrphan() 
      .Table("ClientAddress") 
      .ParentKeyColumn("ClientId") 
      .ChildKeyColumn("AddressId") 
      .AsBag(); 
    }   
} 

Я тогда выполнить запрос ICriteria как этот

return Session.CreateCriteria<Client>() 
    .CreateAlias("Organisation", "o").SetFetchMode("o", FetchMode.Join) 
    .CreateAlias("Addresses", "a").SetFetchMode("a", FetchMode.Join) 
    .Add(expression) 
    .AddOrder(Order.Asc("Surname")).AddOrder(Order.Asc("GivenName")) 
    .SetResultTransformer(new DistinctRootEntityResultTransformer()) 
    .SetMaxResults(pageSize) 
    .SetFirstResult(Pagination.FirstResult(pageIndex, pageSize)) 
    .Future<Client>(); 

Использование NHProf я могу видеть, он выполняет запрос, как это, который должен вернуть все детали клиента и адреса

SELECT top 20 this_.ClientId  as ClientId5_2_, 
       this_.GivenName  as GivenName5_2_, 
       addresses4_.ClientId as ClientId, 
       a2_.AddressId  as AddressId, 
       a2_.AddressId  as AddressId0_0_, 
       a2_.Street   as Street0_0_, 
       a2_.Suburb   as Suburb0_0_, 
       a2_.State   as State0_0_, 
       a2_.Postcode   as Postcode0_0_, 
       a2_.Country   as Country0_0_, 
       a2_.AddressTypeId as AddressT7_0_0_, 
       a2_.OrganisationId as Organisa8_0_0_, 
       o1_.OrganisationId as Organisa1_11_1_, 
       o1_.Description  as Descript2_11_1_, 
       o1_.Code    as Code11_1_, 
       o1_.TimeZone   as TimeZone11_1_ 
FROM  dbo.Client this_ 
     inner join ClientAddress addresses4_ 
      on this_.ClientId = addresses4_.ClientId 
     inner join dbo.Address a2_ 
      on addresses4_.AddressId = a2_.AddressId 
     inner join dbo.Organisation o1_ 
      on this_.OrganisationId = o1_.OrganisationId 
WHERE (o1_.Code = 'Demo' /* @p4 */ 
      and (this_.Surname like '%' /* @p5 */ 
       or (this_.HomePhone = '%' /* @p6 */ 
        or this_.MobilePhone = '%' /* @p7 */))) 
ORDER BY this_.Surname asc, 
     this_.GivenName asc 

который возвращает все записи, как ожидалось

Однако, если я тогда пишу код вроде

foreach(var client in clients) 
{ 
    if (client.Addresses.Any()) 
    { 
     Console.WriteLn(client.Addresses.First().Street); 
    } 
} 

Я по-прежнему получаю сообщение N + 1, где он делает выбор по каждому адресу. Как я могу избежать этого?

ответ

1

Я думаю, вы не понимаете, что здесь происходит ... почти всегда неправильно использовать отдельный трансформатор результатов в сочетании с поисковым вызовом. Подумайте об этом, вы получаете только первые 20 строк кросс-продукта, учитывая этот запрос выше. Я предполагаю, что из-за этого некоторые из ваших клиентов в конце списка не имеют своих коллекций, что приводит к проблеме N + 1.

Если вам нужно выполнить операцию подкачки, подумайте над подсказкой batch-size для сопоставления коллекции, чтобы свести к минимуму проблему N + 1.

Примечание: если ваш типичный вариант использования предназначен для отображения страниц по 20 за раз, установите для этого значения значение batch-size.

+0

Привет. Когда я удаляю отдельный трансформатор результатов, результат будет таким же. Я добавлю размер партии и посмотрю, не изменилось ли это. – Craig

+0

Ну да, результат, очевидно, будет таким же, как и мой вышеприведенный комментарий (так как вы по-прежнему выбираете только 20 верхних строк) ... хорошим испытанием было бы удаление псевдонима, предложенного Диего И удаление пейджинга (в то время как сохраняя отдельный трансформатор на месте), что должно дать вам полностью заполненные коллекции ... – DanP

+0

Настройка размера партии, кажется, лучшая, что я могу сделать. Это приводит к двум выборам, но это лучше, чем 50. – Craig

1

Когда вы используете CreateAlias(collection), SetFetchMode(collection) не действует.

Для лучшего подхода к нетерпеливой загрузки коллекций см http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx

+0

Спасибо. Вы знаете пример с использованием ICriteria, а не HQL? – Craig

+0

Исправьте меня, если я ошибаюсь Диего; но это все равно не поможет ему с поисковым вызовом, не так ли? – DanP

+0

Также, в отношении примеров фьючерсов с использованием ICritiera; см.: http://ayende.com/Blog/archive/2009/04/27/nhibernate-futures.aspx - вы, вероятно, примените это к загрузке коллекций с нетерпением согласно статье Diego, опубликованной выше. – DanP