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, где он делает выбор на каждом адресе. Как я могу избежать этого?

2 ответа

Решение

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

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

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

Когда вы используете CreateAlias(collection), SetFetchMode(collection) не имеет никакого эффекта

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

Другие вопросы по тегам