Добавление проекции к критериям NHibernate останавливает его от выбора объекта по умолчанию
-
10-07-2019 - |
Вопрос
Я пишу критерий NHibernate, который выбирает данные, поддерживающие подкачку страниц. Я использую выражение COUNT (*) OVER ()
из SQL Server 2005 (+), чтобы получить общее количество доступных строк как , предложенный Айенде Рахьен. Мне нужно это число, чтобы подсчитать, сколько всего страниц. Прелесть этого решения в том, что мне не нужно выполнять второй запрос, чтобы получить счетчик строк. Р>
Однако мне не удается написать рабочие критерии (Ayende предоставляет только HQL-запрос).
Вот SQL-запрос, который показывает, что я хочу, и он работает просто отлично. Обратите внимание, что я намеренно пропустил реальную логику подкачки, чтобы сосредоточиться на проблеме:
SELECT Items.*, COUNT(*) OVER() AS rowcount
FROM Items
Вот HQL:
select
item, rowcount()
from
Item item
Обратите внимание, что функция rowcount ()
зарегистрирована в пользовательском диалекте NHibernate и преобразуется в COUNT (*) OVER ()
в SQL.
Требование заключается в том, чтобы запрос выражался с использованием критериев. К сожалению, я не знаю, как сделать это правильно:
var query = Session
.CreateCriteria<Item>("item")
.SetProjection(
Projections.SqlFunction("rowcount", NHibernateUtil.Int32));
Всякий раз, когда я добавляю проекцию, NHibernate не выбирает item
(как это было бы без проекции), а только rowcount ()
, в то время как мне действительно нужны оба варианта. Кроме того, я не могу проецировать item
в целом, только его свойства, и я действительно не хочу перечислять их все.
Я надеюсь, что у кого-то есть решение для этого. В любом случае, спасибо.
Решение
Я думаю, что это невозможно в Критериях, у него есть некоторые ограничения.
Вы можете получить идентификатор и загрузить элементы в следующем запросе:
var query = Session
.CreateCriteria<Item>("item")
.SetProjection(Projections.ProjectionList()
.Add(Projections.SqlFunction("rowcount", NHibernateUtil.Int32))
.Add(Projections.Id()));
Если вам это не нравится, используйте HQL, вы также можете установить максимальное количество результатов:
IList<Item> result = Session
.CreateQuery("select item, rowcount() from item where ..." )
.SetMaxResult(100)
.List<Item>();
Другие советы
Используйте CreateMultiCriteria.
Таким образом, вы можете выполнить 2 простых оператора с одним попаданием в БД.
Мне интересно, почему использование критериев является обязательным требованием. Не можете ли вы использовать session.CreateSQLQuery? Если вы действительно должны сделать это в одном запросе, я бы предложил вернуть объекты Item и их количество, например:
select {item.*}, count(*) over()
from Item {item}
... таким образом вы можете получить обратно объекты Item из вашего запроса вместе с их количеством. Если у вас возникла проблема с кэшированием в Hibernate, вы также можете настроить пространства запросов (кэши сущностей / таблиц), связанные с собственным запросом, чтобы устаревшие записи кэша запросов автоматически очищались.
Если я правильно понимаю ваш вопрос, у меня есть решение. Я немного боролся с этой же проблемой.
Позвольте мне быстро описать проблему, с которой я столкнулся, чтобы убедиться, что мы на одной странице. Моя проблема сводилась к пейджингу. Я хочу отобразить 10 записей в пользовательском интерфейсе, но я также хочу знать общее количество записей, которые соответствуют критериям фильтра. Я хотел сделать это с помощью API критериев NH, но при добавлении прогноза для подсчета строк мой запрос больше не работал, и я не получил никаких результатов (я не помню конкретной ошибки, но звучит так, как вы получаю).
Вот мое решение (скопируйте и вставьте из моего текущего производственного кода). Обратите внимание, что " SessionError " имя бизнес-объекта, для которого я извлекаю выгружаемые данные в соответствии с 3 критериями фильтра: IsDev, IsRead и IsResolved.
ICriteria crit = CurrentSession.CreateCriteria(typeof (SessionError))
.Add(Restrictions.Eq("WebApp", this));
if (isDev.HasValue)
crit.Add(Restrictions.Eq("IsDev", isDev.Value));
if (isRead.HasValue)
crit.Add(Restrictions.Eq("IsRead", isRead.Value));
if (isResolved.HasValue)
crit.Add(Restrictions.Eq("IsResolved", isResolved.Value));
// Order by most recent
crit.AddOrder(Order.Desc("DateCreated"));
// Copy the ICriteria query to get a row count as well
ICriteria critCount = CriteriaTransformer.Clone(crit)
.SetProjection(Projections.RowCountInt64());
critCount.Orders.Clear();
// NOW add the paging vars to the original query
crit = crit
.SetMaxResults(pageSize)
.SetFirstResult(pageNum_oneBased * pageSize);
// Set up a multi criteria to get your data in a single trip to the database
IMultiCriteria multCrit = CurrentSession.CreateMultiCriteria()
.Add(crit)
.Add(critCount);
// Get the results
IList results = multCrit.List();
List<SessionError> sessionErrors = new List<SessionError>();
foreach (SessionError sessErr in ((IList)results[0]))
sessionErrors.Add(sessErr);
numResults = (long)((IList)results[1])[0];
Итак, я создаю свои базовые критерии с дополнительными ограничениями. Затем я клонирую его и добавляю проекцию подсчета строк в критерии CLONED. Обратите внимание, что я клонирую его до того, как добавляю ограничения по подкачке. Затем я настроил IMultiCriteria для хранения исходных и клонированных объектов ICriteria и использую IMultiCriteria для их выполнения. Теперь у меня есть страничные данные из исходной ICriteria (и я перетаскивал только те данные, которые мне нужны по сети), а также необработанный счетчик того, сколько фактических записей соответствовало моим критериям (полезно для отображения или создания ссылок на пейджинг или что-то еще). Эта стратегия хорошо сработала для меня. Я надеюсь, что это полезно.
Я бы предложил исследовать пользовательский преобразователь результатов, вызвав SetResultTransformer () в вашем сеансе.
Создайте свойство формулы в сопоставлении классов:
<property name="TotalRecords" formula="count(*) over()" type="Int32" not-null="true"/>;
IList<...> result = criteria.SetFirstResult(skip).SetMaxResults(take).List<...>();
totalRecords = (result != null && result.Count > 0) ? result[0].TotalRecords : 0;
return result;