Массовая загрузка дочерних объектов NHibernate для нескольких родителей

Допустим, у нас есть таблица категорий и таблица продуктов. И каждый продукт ссылается на категорию. Таким образом, в каждой категории есть много продуктов. Я хочу загрузить много категорий без продуктов (чтобы уменьшить время доступа к БД), затем проверить, какие категории нам действительно нужны, и определить гораздо меньшее подмножество категорий. После этого мне нужно загрузить все продукты для выбранных категорий и прикрепить их к категориям с помощью одного запроса к БД. Я могу загружать товары отдельно, но в этом случае они не будут привязаны к категориям.


person Serhiy    schedule 06.04.2015    source источник


Ответы (2)


Этого можно достичь с помощью HQL и фьючерсов.

учитывая сущности и карты следующим образом,

public class Category
{
    private IList<Product> _products; 

    public Category()
    {
        _products = new List<Product>();
    }

    public virtual int Id { get; set; }
    public virtual string CategoryName { get; set; }
    public virtual IList<Product> Products
    {
        get { return _products; }
        set { _products = value; }
    }
}

public class CategoriesClassMap : ClassMap<Category>
{
    public CategoriesClassMap()
    {
        Table("Categories");
        Id(x => x.Id).GeneratedBy.Native();
        Map(x => x.CategoryName);
        HasMany<Product>(c => c.Products).LazyLoad();
    }
}

public class Product
{
    public virtual int Id { get; set; }
    public virtual string ProductName { get; set; }
    public virtual Category Category { get; set; }
}

public class ProductSClassMap : ClassMap<Product>
{
    public ProductSClassMap()
    {
        Table("Products");
        Id(x => x.Id).GeneratedBy.Native();
        Map(x => x.ProductName);
        References<Category>(x => x.Category).Not.Nullable();
    }
}

При следующем HQL он загрузит все категории и продукты в одном запросе,

var categories = session.CreateQuery("from Category c join fetch c.Products where c.Id in (1,2)")
                    .Future<Category>().Distinct().ToList();

Он извлекает только данные, относящиеся к идентификаторам категорий 1 и 2. Сгенерированный SQL выглядит так:

select category0_.Id as Id1_0_, products1_.Id as Id3_1_, category0_.CategoryName as Category2_1_0_, products1_.ProductName as ProductN2_3_1_, products1_.Category_id as Category3_3_1_, products1_.Category_id as Category3_0__, products1_.Id as Id0__ from Categories category0_ inner join Products products1_ on category0_.Id=products1_.Category_id where category0_.Id in (1 , 2);

То же самое (используя будущее) применимо для queryover или criteria

person Low Flying Pelican    schedule 07.04.2015
comment
Собственно, так мы и поступаем сейчас. Но у него все еще есть проблема - выбранные категории извлекаются (и, вероятно, реконструируются) во второй раз. - person Serhiy; 07.04.2015
comment
но для этого требуется только 2 выполнения SQL: 1. для загрузки всех категорий для фильтрации 2. Запрос для получения всех категорий + продуктов для отфильтрованных категорий. Так что даже если вам удастся прикрепить загруженные продукты к уже загруженным категориям, какая разница? - person Low Flying Pelican; 07.04.2015

Этот подход, решение изначально встроено в NHiberante. Это называется:

19.1.5. Использование пакетной выборки

NHibernate может эффективно использовать пакетную выборку, то есть NHibernate может загружать несколько неинициализированных прокси-серверов при доступе к одному прокси-серверу (или коллекциям). класс и уровень коллекции.

Пакетную выборку для классов/сущностей легче понять. Представьте, что у вас есть следующая ситуация во время выполнения: у вас есть 25 экземпляров Cat, загруженных в ISession, каждый Cat имеет ссылку на своего владельца, человека. Класс Person сопоставляется с прокси, lazy="true". Если теперь вы пройдете по всем котам и вызовете cat.Owner для каждого, NHibernate по умолчанию выполнит 25 операторов SELECT, чтобы получить прокси-владельцев. Вы можете настроить это поведение, указав размер партии в отображении Person:

<class name="Person" batch-size="10">...</class>

NHibernate теперь будет выполнять только три запроса, шаблон 10, 10, 5.

Вы также можете включить пакетную выборку коллекций. Например, если у каждого Person есть ленивая коллекция Cats, а 10 человек в настоящее время загружены в ISesssion, итерация по всем людям создаст 10 SELECT, по одному на каждый вызов person.Cats. Если вы включите пакетную выборку для коллекции Cats в отображении Person, NHibernate может выполнять предварительную выборку коллекций:

<class name="Person">
    <set name="Cats" batch-size="3">
        ...
    </set>
</class>

ОБЗОР. Существует параметр сопоставления оптимизации: batch-size="25".

Мы можем использовать его на уровне класса (используется позже для many-to-one отношений) или на коллекциях (непосредственно на one-to-many реальности)

Это приведет к очень небольшому количеству операторов SELECT для загрузки сложного графа объектов. И самое важное преимущество заключается в том, что мы можем использовать разбиение по страницам (Take(), Skip()) при запросе корневого объекта (не несколько строк)

Проверьте также это и еще несколько ссылок...

person Radim Köhler    schedule 06.04.2015
comment
Проблема здесь в том, что мне нужно загружать продукты только для выбранных категорий, а не для всех, которые были загружены - person Serhiy; 06.04.2015
comment
Я бы особо не пытался решать, не умнее родного, встроенного функционала. Просто попробуйте загрузить все продукты для всех категорий с помощью моего (ну, NHibernate) подхода. Проверьте производительность. Затем выберите всего несколько категорий с запросом и дайте снова загрузить все товары. Я уверен, вы удивитесь, что почти ничем не отличается. Прирост производительности за счет загрузки только выбранного вряд ли можно заменить использованием встроенной функции. Я ежедневно использую размер партии, будучи уверенным, что могу запросить только корень, и при необходимости загружаются все отношения. Очень хорошо работает... - person Radim Köhler; 06.04.2015