NHibernate N + 1获取问题

时间:2010-11-04 11:58:50

标签: c# nhibernate select-n-plus-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 个答案:

答案 0 :(得分:1)

我认为你误解了这里发生了什么......将不同的结果转换器与分页结合使用几乎总是不正确的。考虑一下,在上面的查询中,您只获得了交叉产品的前20行。我猜测列表末尾的几个客户没有填充他们的集合,导致你的N + 1问题。

如果您需要进行分页操作,请考虑在集合映射上使用batch-size提示,以帮助最小化N + 1问题。

注意:如果您的典型用例是一次显示20个网页,请将batch-size设置为此值。

答案 1 :(得分:1)

使用CreateAlias(collection)时,SetFetchMode(collection)无效。

要想更好地加载集合,请参阅http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx