NHibernate延迟加载带有期货的嵌套集合以避免N + 1问题

Nic*_*oud 14 nhibernate queryover

我有一个看起来像这样的对象模型(伪代码):

class Product {
    public ISet<Product> Recommendations {get; set;}
    public ISet<Product> Recommenders {get; set;}
    public ISet<Image> Images {get; set; }
}
Run Code Online (Sandbox Code Playgroud)

当我加载给定的产品并想要显示其推荐的图像时,我遇到了N + 1问题.(建议是延迟加载的,然后循环调用每个的.Images属性.)

Product -> Recommendations -> Images
Run Code Online (Sandbox Code Playgroud)

我想要做的是急切地加载图表的这个特定部分,但我无法弄清楚如何做到这一点.我可以热切地加载建议,但不能加载他们的图像.这是我一直在尝试的,但它似乎不起作用:

//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);
Run Code Online (Sandbox Code Playgroud)

使用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;
Run Code Online (Sandbox Code Playgroud)

Dan*_*ing 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();
Run Code Online (Sandbox Code Playgroud)

在讨论使用NHibernate快速获取多个集合时,您经常会听到人们提到笛卡尔产品,但这不是一个问题.但是,如果您希望加载以下图表而不是......

 Product -> Recommendations -> Images
         -> Images
Run Code Online (Sandbox Code Playgroud)

...然后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;
Run Code Online (Sandbox Code Playgroud)