2011-03-06 65 views
13

我有看起來像這樣的(僞碼)的對象模型:NHibernate的遲緩裝載嵌套集合與期貨,以避免N + 1問題

class Product { 
    public ISet<Product> Recommendations {get; set;} 
    public ISet<Product> Recommenders {get; set;} 
    public ISet<Image> Images {get; set; } 
} 

當我加載給定的產品和要顯示的圖像其建議,我遇到了N + 1問題。 (這些建議是延遲加載,然後循環調用每個人的.Images財產。)

Product -> Recommendations -> Images 

我想要做的就是急切地加載圖形的這個特殊的一部分,但我想不出怎麼做。我可以加載的建議熱切,但不是他們的形象。這是我一直在努力,但似乎並沒有工作:

//get the IDs of the products that will be in the recommendations collection 
var recommendedIDs = QueryOver.Of<Product>() 
    .Inner.JoinQueryOver<Product>(p => p.Recommenders) 
    .Where(r => r.Id == ID /*product we are currently loading*/) 
    .Select(p => p.Id); 

//products that are in the recommendations collection should load their 
//images eagerly 
CurrentSession.QueryOver<Product>() 
    .Fetch(p => p.Images).Eager 
    .Where(Subqueries.WhereProperty<Product>(p => p.Id).In(recommendedIDs)) 
    .Future<Product>(); 

//load the current product 
return CurrentSession.QueryOver<Product>() 
    .Where(p => p.Id == ID); 

使用QueryOver,什麼是實現這一目標的最佳方式是什麼?我不想急於加載圖像,只是在這種特殊情況下。


編輯:我已經改變了我的方法,雖然它不正是我腦子裏想的,它避免了N + 1點的問題。我現在使用兩個查詢,一個用於產品,另一個用於查詢推薦的圖片。產品查詢非常簡單;這裏是圖像查詢:

//get the recommended product IDs; these will be used in 
//a subquery for the images 
var recommendedIDs = QueryOver.Of<Product>() 
    .Inner.JoinQueryOver<Product>(p => p.Recommenders) 
    .Where(r => r.Id == RecommendingProductID) 
    .Select(p => p.Id); 

//get the logo images for the recommended products and 
//create a flattened object for the data 
var recommendations = CurrentSession.QueryOver<Image>() 
    .Fetch(i => i.Product).Eager 
    /* filter the images down to only logos */ 
    .Where(i => i.Kind == ImageKind.Logo) 
    .JoinQueryOver(i => i.Product) 
    /* filter the products down to only recommendations */ 
    .Where(Subqueries.WhereProperty<Product>(p => p.Id).In(recommendedIDs)) 
    .List().Select(i => new ProductRecommendation { 
     Description = i.Product.Description, 
     ID = i.Product.Id, 
     Name = i.Product.Name, 
     ThumbnailPath = i.ThumbnailFile 
    }).ToList(); 

return recommendations; 

回答

17

JoinAlias是另一種方式來預先抓取相關的記錄,再加上我們可以使用它來Recommendations挖另一個層次更深下降到Images。我們將使用LeftOuterJoin,因爲即使它沒有建議,我們也要加載該產品。

Product recommendationAlias = null; 
Image imageAlias = null; 

return CurrentSession.QueryOver<Product>() 
    .JoinAlias(x => x.Recommendations,() => recommendationAlias, JoinType.LeftOuterJoin) 
    .JoinAlias(() => recommendationAlias.Images,() => imageAlias, JoinType.LeftOuterJoin) 
    .Where(x => x.Id == ID) 
    .TransformUsing(Transformers.DistinctRootEntity) 
    .SingleOrDefault(); 

在討論用NHibernate提取多個集合時,你經常會聽到人們提到笛卡爾的產品,但這不是一個問題。但是,如果您希望加載下圖,而不是...

Product -> Recommendations -> Images 
     -> Images 

...然後Product.Recommendations.Images X Product.Images將形成笛卡爾乘積,我們應該避免的。我們可以這樣做是這樣的:

Product recommendationAlias = null; 
Image imageAlias = null; 

var productFuture = CurrentSession.QueryOver<Product>() 
    .JoinAlias(x => x.Recommendations,() => recommendationAlias, JoinType.LeftOuterJoin) 
    .JoinAlias(() => recommendationAlias.Images,() => imageAlias, JoinType.LeftOuterJoin) 
    .Where(x => x.Id == ID) 
    .TransformUsing(Transformers.DistinctRootEntity) 
    .FutureValue(); 

var imagesFuture = CurrentSession.QueryOver<Product>() 
    .Fetch(x => x.Images).Eager 
    .Where(x => x.Id == ID) 
    .TransformUsing(Transformers.DistinctRootEntity) 
    .Future(); 

return productFuture.Value; 
+0

我一直在尋找'TransformUsing' for 2 hours now!非常感謝.. – 2014-01-11 04:07:41

+0

沒問題!很高興我能幫上忙。 – 2014-01-14 16:20:17

0

如果你想要的是避免了N + 1的麻煩,使用延遲加載,而不是預先加載的Batch fetching

它消除了N + 1問題,同時對代碼的影響最小:只需更改配置參數或調整映射。

在配置中,將default_batch_fetch_size設置爲您平常延遲加載計數的一些合理值。 20通常是一個很好的價值。

或者在映射,通過案例控制的情況下延遲加載配料設置類別(<class>)和集合(<set><bag> ...)的batch-size屬性。

這將配置你的懶惰加載的實體和實體集合不僅加載自己,而且還有一些其他等待實體(同一類)或實體集合(其他父實體的相同集合)。

我已經在this other answer中寫了詳細的解釋。