2010-11-04 125 views
1

我有一個像這樣的實體和流暢的映射。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問題,它在每個地址上進行選擇。我怎樣才能避免這種情況?

回答

1

我認爲你誤解了這裏發生的事情......在分頁中使用不同的結果轉換器幾乎總是不正確的。想想看,你只能得到上述查詢的前20行交叉產品。我在猜測,名單末尾的幾位客戶並沒有因爲這個問題而讓他們的收藏品出現,導致您的N + 1問題。

如果您需要執行分頁操作,請考慮在集合映射中使用batch-size提示,以幫助最大限度地減少N + 1問題。

注意:如果您的典型用例一次顯示20個頁面,請將batch-size設置爲該值。

+0

嗨。當我刪除不同的結果變換器時,結果是相同的。我將添加批量大小,看看它是否有所作爲。 – Craig 2010-11-04 20:31:16

+0

嗯,是的,結果顯然是相同的,因爲我上面的評論(因爲你仍然只選擇前20行)...一個好的測試將刪除別名,因爲迭戈已經建議和刪除分頁(while保持不同的變壓器),這應該給你充分的人口收集... – DanP 2010-11-04 22:19:43

+0

設置批量大小似乎是我能做的最好的。它會產生兩個選擇,但是這比50好。 – Craig 2010-11-05 11:32:48

1

當您使用CreateAlias(collection)時,SetFetchMode(collection)不起作用。

爲了更好的辦法集合預先加載,請參閱http://ayende.com/Blog/archive/2010/01/16/eagerly-loading-entity-associations-efficiently-with-nhibernate.aspx

+0

感謝您的支持。你知道使用ICriteria而不是HQL的例子嗎? – Craig 2010-11-04 21:59:45

+0

糾正我,如果我錯了迭戈;但是這仍然不能幫助他解決問題,是嗎? – DanP 2010-11-04 22:04:22

+0

另外,關於使用ICritiera的期貨的例子;請參閱:http://ayende.com/Blog/archive/2009/04/27/nhibernate-futures.aspx - 您可能會應用此按照上面張貼的文章熱切地加載集合。 – DanP 2010-11-04 22:32:09