【问题标题】:NHibernate lazy loading nested collections with futures to avoid N+1 problemNHibernate 延迟加载带有期货的嵌套集合以避免 N+1 问题
【发布时间】:2011-03-06 05:55:17
【问题描述】:

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

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;

【问题讨论】:

    标签: nhibernate queryover


    【解决方案1】:

    JoinAlias 是另一种急切获取相关记录的方法,而且我们可以使用它从RecommendationsImages 更深入地挖掘另一个层次。我们将使用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 讨论对多个集合的 Eager fetching 时,您经常听到人们提到笛卡尔积,但这不是问题。但是,如果您希望加载以下图表...

     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;
    

    【讨论】:

    • 没问题!很高兴我能帮上忙。
    【解决方案2】:

    使用 NHibernateUtil 类对您关心的图形部分强制加载。

     NHibernateUtil.Initialize(Product.Recommendations);
    

    查看下面的链接了解更多详情。

    http://nhforge.org/wikis/howtonh/lazy-loading-eager-loading.aspx

    【讨论】:

      【解决方案3】:

      如果您只想避免 N+1 麻烦,请使用延迟加载的 Batch fetching 而不是急切加载。

      它消除了 N+1 个问题,同时对代码的影响最小:您只需更改配置参数或调整映射。

      在配置中,将default_batch_fetch_size 设置为您通常的延迟加载计数的某个合理值。 20 通常是一个不错的值。

      或者在映射中,为类 (&lt;class&gt;) 和集合 (&lt;set&gt;, &lt;bag&gt;, ...) 设置 batch-size 属性,以逐个控制延迟加载批处理。

      这会将延迟加载的实体和实体集合配置为不仅加载它们自己,还加载其他一些等待实体(同一类)或实体集合(同一类的其他实体的相同集合)。

      我已经在this other answer写了详细的解释。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多