Извлечение данных из 50+ таблиц с использованием Linq-to-Nhibernate

У меня есть веб-сервис, который касается более 50 таблиц базы данных (база данных сильно нормализована), чтобы создать ответ. Служба возвращает все рейсы, измененные в пределах диапазона дат, указанного клиентом.

Из соображений производительности я хочу избежать отложенной загрузки, выбирая как можно большую часть графика, прежде чем сопоставлять его с типом ответа.

Я разбил свой запрос на более мелкие части, используя Nhibernate Fetch + ToFuture для загрузки необходимых мне данных:

var fetchQuery = Session.Query<Voyage>()
.Fetch(v => v.VoyageStatus)
.FetchMany(v => v.VoyageLocations)
.Where(v => voyageIds.Contains(v.VoyageID))
.ToFuture();

Session.Query<Ship>()
.FetchMany(s => s.ShipCsos)
.Where(s => shipIds.Contains(s.ShipID))
.ToFuture();

Session.Query<Ship>()
.Fetch(s => s.ShipFlagCode)
.ThenFetch(sf => sf.Country)
.Fetch(s => s.ShipType)
.Fetch(s => s.ShipStatus)
.Fetch(s => s.ShipSource)
.Fetch(s => s.ShipHullType)
.Fetch(s => s.ShipLengthType)
.Fetch(s => s.ShipBreadthType)
.Fetch(s => s.ShipSpeedType)
.Fetch(s => s.ShipPowerType)
.FetchMany(s => s.ShipAttributes)
.ThenFetch(sa => sa.ShipAttributeName)
.Where(s => shipIds.Contains(s.ShipID))
.ToFuture();

//[Lots of similar Session.Query<X>...ToFuture() calls]

return fetchQuery.ToList();

проблема

Я начинаю достигать предела параметра SQL Server 2100, когда диапазон дат достигает определенного диапазона. Я думал, что предел параметра применяется только к одному предложению IN, но он, очевидно, относится к запросу в целом; используя Futures, я получаю один SQL-запрос с одним оператором SELECT для каждого вызова ToFuture (каждый оператор SELECT содержит предложение IN умеренного размера).

Есть ли обходной путь для этого? Например, есть ли способ отправить меньшие группы фьючерсов, чтобы они оставались в пределах ограничения параметра и по-прежнему увлажняли сущности?

Я попытался сделать вызов fetchQuery.ToList() на полпути через Futures. Это позволяет избежать исключений для ограничения параметров, но объекты не обрабатываются должным образом в соответствии с Nhibernate Profiler (свойства загружаются с отложенной загрузкой).

Любые указатели будут высоко оценены!

1 ответ

Решение

На самом деле, возможно, вам лучше сохранить ленивую загрузку по причинам производительности с NHibernate, даже в вашем случае.

(Желание переключиться на активную загрузку по соображениям производительности может быть признаком того, что вы не знаете, как оптимизировать отложенную загрузку с помощью NHibernate. NHibernate может избежать классической проблемы производительности n+1 с отложенной загрузкой.)

Почему ленивая загрузка может хорошо работать с NH

(Даже в вашем случае.)

Ленивая загрузка с NHibernate может быть чрезвычайно эффективной. Это имеет тенденцию поддерживать хороший баланс между производительностью во время выполнения и разработкой. Эффективно выполнять и эффективно развивать и поддерживать.

Отрегулируйте ленивую загрузку batch-sizeсобственности на ваши объекты и коллекции отображений.

(Связанные ссылки дают подробное объяснение того, как это работает.)

<class name="YourEntity" batch-size="20">
    ...
    <set name="SomeChildren" batch-size="15" ...>

Конфигурирование, которое заставляет NHibernate не только загружать связанные сущности / коллекции при обращении к ним, но и включать в загрузку доbatch-size - 1связанные сущности / коллекции, которые он отслеживал в своем кеше первого уровня сеанса. Конечно, настроитьbatch-sizeзначения для соответствия вашим обычным случаям загрузки мощности.

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

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

Вы можете глобально настроить размер пакета по умолчанию для всех отложенных загрузок коллекций и объектов с помощью параметра глобальной конфигурацииdefault_batch_fetch_size(для помещения в файл hibernate.cfg.xml или для установки черезConfiguration.SetProperty(Environment.DefaultBatchFetchSize, ...)).

Почему быстрая загрузка может быть худшим выбором

Напротив, активная загрузка может быстро привести к "раздувному коду" и дополнительной работе для тонкой настройки и поддержания требуемых активных нагрузок для каждого случая. И если не поддерживать их оптимизированными, это, безусловно, приведет к худшим показателям, чем ленивая загрузка с NHibernate. Даже оптимизированная загрузка может привести к гораздо большему количеству данных, чем требуется для загрузки.

EF до его 6 версии делал это. (7, возможно, нет.) Его стратегия запросов с активной загрузкой заключалась в том, чтобы загруженные в результирующие наборы содержали дублированные данные, как только "корневые" объекты устанавливали, где было много ссылок на одни и те же загруженные нетерпением дочерние экземпляры сущностей. (И все это, хотя в моем текущем состоянии знаний, я склонен считать, что EF более удобен, чем NHibernate, для быстрой загрузки. Но это довольно долгое время я не рассматривал и не изучал полную загрузку с помощью NHibernate, его ленивую загрузку быть более эффективным, чем у EF.)

Дополнительная оптимизация доступна с ленивой загрузкой

NHibernate имеет встроенную поддержку кэширования второго уровня. Кэширование второго уровня позволяет кэшировать данные и обмениваться ими между различными сеансами NHibernate.

При активной загрузке кэш второго уровня не может быть использован для загрузки зависимых объектов из памяти (в случае, если вы используете провайдер кэша памяти для кэша второго уровня). Кэш второго уровня лучше всего использовать с отложенной загрузкой.

Это полнофункциональный кеш данных, автоматически обрабатывающий данные. ( При условии, что вы работаете с транзакциями. Если тупики мешают вам сделать это, возможно, стоит подумать о включении read committed snapshot режим на SQL Server, но это немного не по теме. Без явных транзакций кеш будет отключен, как только вы начнете обновлять сущности в вашем приложении.)

Вам нужно только включить его в глобальной конфигурации (cache.provider_class, cache.use_second_level_cache) и объявите в своем отображении, что кешируется (для сущностей и / или коллекций сущностей, с <cache usage="..." /> тег). Используйте регионы кэша для установки срока действия. Вы можете даже кешировать запросы (cache.use_query_cacheи указание на запросы, если они кешируются). Смотрите здесь для примера.

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

Примечание

Хорошо принятое решение вашей проблемы подразумевает гораздо больше работы. В идеале ваше фронтальное приложение должно работать с ненормализованной копией ваших данных, легко и эффективно запрашивать, в то время как ваш бэк-офис хранит нормализованную базу данных.

Другие вопросы по тегам