NHibernate 延迟加载带有期货的嵌套集合以避免 N+1 问题 [英] NHibernate lazy loading nested collections with futures to avoid N+1 problem

查看:15
本文介绍了NHibernate 延迟加载带有期货的嵌套集合以避免 N+1 问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an object model that looks like this (pseudo code):

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

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

When I load a given product and want to display the images of its recommendations, I run into an N+1 problem. (The recommendations are lazy-loaded, then a loop calls the .Images property of each one.)

Product -> Recommendations -> Images

我想做的是急切地加载图表的这一特定部分,但我不知道该怎么做.我可以急切地加载推荐,但不能加载它们的图像.这是我一直在尝试的方法,但似乎不起作用:

What I want to do is eagerly load this particular part of the graph, but I can't figure out how to do it. I can load the recommendations eagerly, but not their images. This is what I have been trying, but it doesn't seem to work:

//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,实现此目的的最佳方法是什么?我不想一直急切地加载图像,只是在这种特殊情况下.

Using QueryOver, what is the best way to accomplish this? I don't want to eagerly load images all the time, just in this particular scenario.

编辑:我已经改变了我的方法,虽然这不是我的想法,但它确实避免了 N+1 问题.我现在使用两个查询,一个查询产品,一个查询推荐的图片.产品查询很简单;这是图像查询:

EDIT: I have changed my approach, and while it's not exactly what I had in mind, it does avoid the N+1 problem. I am now using two queries, one for the product, and one for the images of it's recommendations. The product query is straight-forward; here is the image query:

//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;

推荐答案

JoinAlias 是另一种急切获取相关记录的方法,此外我们还可以使用它通过 Recommendations<深入挖掘另一个层次/code> 到 Images.我们将使用 LeftOuterJoin 因为我们想要加载产品,即使它没有推荐.

JoinAlias is another way to eagerly fetch related records, plus we can use it to dig another level deeper through Recommendations down to Images. We'll use LeftOuterJoin because we want to load the product even if it has no recommendations.

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 急切获取多个集合时,您经常听到人们提到笛卡尔积,但这不是这里的问题.但是,如果您希望加载以下图表...

When discussing eager fetching of multiple collections with NHibernate, you often hear people mention Cartesian products, but that's not a concern here. If however, you wished to load the following graph instead...

 Product -> Recommendations -> Images
         -> Images

... 那么 Product.Recommendations.Images X Product.Images 将形成我们应该避免的笛卡尔积.我们可以这样做:

... then Product.Recommendations.Images X Product.Images would form a Cartesian product that we should avoid. We could do so like this:

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;

这篇关于NHibernate 延迟加载带有期货的嵌套集合以避免 N+1 问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆