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