NHibernate ленивая загрузка вложенных коллекций с фьючерсами, чтобы избежать проблемы N + 1 - PullRequest
14 голосов
/ 06 марта 2011

У меня есть объектная модель, которая выглядит следующим образом (псевдокод):

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;

Ответы [ 3 ]

17 голосов
/ 12 августа 2011

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;
3 голосов
/ 11 апреля 2011

Принудительная активная нагрузка на часть графика, которая вас интересует, с использованием класса NHibernateUtil.

 NHibernateUtil.Initialize(Product.Recommendations);

Для получения дополнительной информации см. Ссылку ниже.

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

1 голос
/ 11 мая 2017

Если все, что вам нужно, - это избежать проблемы N + 1, используйте Пакетная загрузка отложенных загрузок вместо быстрой загрузки.

Устраняет проблемы N + 1, оказывая минимальное влияние накод: вам просто нужно изменить параметр конфигурации или настроить сопоставления.

В конфигурации установите default_batch_fetch_size на какое-то разумное значение для обычного количества ленивых нагрузок.20 обычно является хорошим значением.

Или в сопоставлениях установите атрибуты batch-size для классов (<class>) и коллекций (<set>, <bag>, ...) для управления регистромв данном случае пакетная загрузка с отложенной загрузкой.

Это настроит ваши лениво загруженные сущности и коллекции сущностей не только для загрузки самих себя, но и для некоторых других ожидающих сущностей (того же класса) или коллекций сущностей (одинаковые коллекциидругих лиц того же класса).

Я написал подробное объяснение этого в этом другом ответе .

...