我有一个看起来像这样的对象模型(伪代码):
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;
答案 0 :(得分: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;
答案 1 :(得分:3)
强制使用NHibernateUtil类关注图形部分的热切负载。
NHibernateUtil.Initialize(Product.Recommendations);
有关详细信息,请参阅以下链接。
http://nhforge.org/wikis/howtonh/lazy-loading-eager-loading.aspx
答案 2 :(得分:1)
如果您想要的只是避免N + 1问题,请使用Batch fetching延迟加载而不是急切加载。
它消除了N + 1问题,同时对代码影响最小:您只需更改配置参数或调整映射。
在配置中,将default_batch_fetch_size
设置为通常的延迟加载计数的某个合理值。 20
通常是一个很好的价值。
或者在映射中,在类(batch-size
)和集合(<class>
,<set>
,...)上设置<bag>
属性,以便逐个控制懒惰 - 批量配送。
这将配置您的延迟加载的实体和实体集合,不仅要加载自己,还要等待某些实体(同一类)或实体集合(同一类的其他实体的相同集合)。
我在this other answer中写了详细的解释。