【问题标题】:NHibernate N+1 fetch problemNHibernate N+1 获取问题
【发布时间】:2011-05-05 00:40:33
【问题描述】:

我有一个看起来像这样的实体和流畅的映射。

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 问题,它对每个地址进行选择。我怎样才能避免这种情况?

【问题讨论】:

    标签: c# nhibernate select-n-plus-1


    【解决方案1】:

    我认为你误解了这里发生的事情......将不同的结果转换器与分页结合使用几乎总是不正确的。想想看,给定上面的查询,您只会得到前 20 行的叉积。我猜您列表末尾的几个客户因此没有填充他们的集合,从而导致您的 N+1 问题。

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

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

    【讨论】:

    • 嗨。当我删除不同的结果转换器时,结果是相同的。我将添加批量大小,看看是否有区别。
    • 嗯,是的,考虑到我上面的评论,结果显然是相同的(因为你仍然只选择前 20 行)......一个好的测试是像 Diego 一样删除别名建议并删除分页(同时保留不同的转换器),这应该会给你完全填充的集合......
    • 设置批量大小似乎是我能做的最好的。它导致两次选择,但这比 50 好。
    • @Craig:老实说,这种方法对于您的情况可能足够好了,如果不使查询方法严重复杂化,您将无法在一次旅行中做到这一点 -最后可能不值得……
    【解决方案2】:

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

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

    【讨论】:

    • 谢谢。您知道使用 ICriteria 而不是 HQL 的示例吗?
    • 如果我错了,请纠正我 Diego;但这仍然无法帮助他进行寻呼,是吗?
    • 另外,关于使用 ICritiera 的期货示例;请参阅:ayende.com/Blog/archive/2009/04/27/nhibernate-futures.aspx - 您可以根据 Diego 上面发布的文章将其应用于急切地加载集合。
    • @DanP 虽然您不能使用 Futures 进行分页,但先进行基本实体查询更容易,然后在使用已加载页面的 id 过滤时使用连接查询。这让您意识到将批量大小设置为您的页面大小更容易:-D
    • 这里有一个补丁:216.121.112.228/browse/NH-2316。我参与了围绕它的讨论,但我没有测试它。它应该可以工作。
    猜你喜欢
    • 1970-01-01
    • 2022-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-03
    • 2011-09-24
    • 2011-11-05
    相关资源
    最近更新 更多