代码之家  ›  专栏  ›  技术社区  ›  Craig

NHibernate N+1提取问题

  •  1
  • Craig  · 技术社区  · 14 年前

    我有一个这样的实体和流畅的映射。

    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 回复  |  直到 13 年前
        1
  •  1
  •   DanP    14 年前

    我认为您误解了这里发生的事情……将不同的结果转换器与分页结合使用几乎总是不正确的。考虑一下,考虑到上面的查询,您只得到交叉产品的前20行。我猜想,由于这个原因,列表末尾的几个客户没有填充他们的集合,从而导致了您的N+1问题。

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

    注意:如果您的典型用例一次显示20页,请设置 批量大小 到这个值。

        2
  •  1
  •   Diego Mijelshon    14 年前

    当你使用 CreateAlias(collection) , SetFetchMode(collection) 没有效果。

    要获得更好的方法来预先加载集合,请参见 http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx