Nov 4, 2009

NHibernate для начинающих. Часть 4 Criteria queries

Линки на остальные статьи

Основным средством построения запросов в NHibernate является Criteria api (до тех пор, пока не реализуют полноценный Linq to Hibernate). Поэтому я опишу некоторые примеры запросов, которые можно строить с их использованием. Для этого воспользуемся моделью, описанной тут.

Ранее я уже упоминал простой запрос, который позволяет получить полный список всех нужных объектов. Теперь попробуем построить несколько других, более полезных запросов.

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

ICriteria criteria = Global.CurrentSession.CreateCriteria(typeof(Product));

const string requiredSubstring = "prod";
criteria.Add(Restrictions.Like("Description", requiredSubstring, MatchMode.Anywhere));
criteria.SetMaxResults(10);
criteria.SetFirstResult(0);
criteria.AddOrder(NHibernate.Criterion.Order.Asc("Description"));

IList<Product> result = criteria.List<Product>();

Итак что здесь происходит:

  • 1 строка очевидно – создаем сам критерий.
  • 4 строка – в критерий добавляется условие, которое говорит о том, что объекты, которые войдут в результирующий рекорд сет в свойстве Description должны содержать requiredSubstring. Последний параметр говорит о том, где именно должна быть эта подстрока. Можно создать критерий, который говорит что название должно начинаться с указанного слова или заканчиваться на него. Прелесть этого запроса в том, что мы оперируем исключительно доменными объектами, мы вообще не заботимся о том, как NHibernate построит необходимый запрос и какие поля/таблицы затронет.
  • 5 строка – указываем, что результатов должно быть не больше 10.
  • 6 строка – выдавать результаты начиная с 0го. Т.е. например, у нас в базе есть 20 продуктов, из них 15 в названии содержит «prod». Значит данный запрос выведет 10 первых продуктов.
  • 7 строка – сортировка.

Далее рассмотрим следующий пример:

Найти все продукты, название которых содержит подстроку, и цена которых больше чем указанное число:

const string requiredSubstring = "prod";

const double requiredPrice = 5.0;
criteria.Add(new Conjunction()
  .Add(Restrictions.Like("Description", requiredSubstring, MatchMode.Anywhere)) 
  .Add(Restrictions.Gt("Price", requiredPrice))
      );

Это первый вариант записи, но мне он кажется довольно слабо читаемым, есть альтернативный:

criteria.Add(Restrictions.Like("Description", requiredSubstring, MatchMode.Anywhere) &&
    Restrictions.Gt("Price", requiredPrice)
             );

Как Вы видите между двумя условиями стоит обычный оператор &&. Так же можно использовать и || где это необходимо (например если бы надо было найти товар у которого подстрока содержится либо в названии либо в описании).

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

ICriteria criteria = Global.CurrentSession.CreateCriteria(typeof(Product));
const string requiredSubstring = "prod";
criteria.Add(Restrictions.Like("Description", requiredSubstring, MatchMode.Anywhere));
criteria.SetProjection(Projections.RowCount());
int result = criteria.UniqueResult<int>();

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

DetachedCriteria avgCritegia = DetachedCriteria.For<Product>();
avgCritegia.SetProjection(Projections.Avg("Price"));

ICriteria criteria = Global.CurrentSession.CreateCriteria(typeof(Product));
criteria.Add(Subqueries.PropertyGt("Price", avgCritegia));

IList<Product> result = criteria.List<Product>();

NHibernate выполнит следующий запрос:

SELECT this_.Id as Id0_0_, this_.Description as Descript2_0_0_, this_.Name as Name0_0_, this_.Price as Price0_0_
FROM Products this_ 
WHERE this_.Price > (SELECT avg(cast(this_0_.Price as DOUBLE PRECISION)) as y0_ FROM Products this_0_)

Теперь посмотрим на запрос к объектам с коллекциями. Допустим надо найти все продукты, которые заказывал покупатель с определенной строкой в имени:

ICriteria criteria = Global.CurrentSession.CreateCriteria(typeof(Product));

const string requiredCustomerName = "cust";
criteria.CreateCriteria("Orders")
 .Add(Restrictions.Like("Customer", requiredCustomerName, MatchMode.Anywhere));

IList<Product> result = criteria.List<Product>();

NHibnerate выполнит следующий запрос:

SELECT this_.Id as Id0_1_,
 this_.Description as Descript2_0_1_,
 this_.Name as Name0_1_,
 this_.Price as Price0_1_,
 order1_.Id as Id3_0_,
 order1_.NumberOfItems as NumberOf2_3_0_,
 order1_.Customer as Customer3_0_,
 order1_.ProductId as ProductId3_0_
FROM Products this_ inner join Orders order1_ on this_.Id=order1_.ProductId
WHERE order1_.Customer like @p0
@p0=N'%cust%'

Естественно что в .net 3.5 уже очень не хотелось бы использовать названия свойств в виде строк. Nз-за этого приходится постоянно лазить в исходник самого объекта и смотреть там точное название необходимого ствойства. Чтобы избежать этого существует QueryOver API. Они позволяют переписать все выше упомянутые запросы, но в строго типизированном виде.

9 comments:

  1. Клёво, пиши ещё и кросспости давай =)

    ReplyDelete
  2. эээ.... о чем это ты? :)

    ReplyDelete
  3. Крост пост это когда материал отсылаеться еще и на другие ресурсы на пример на habrahabr.ru

    ReplyDelete
  4. Мы уже разобрались, но все равно спасибо :)

    ReplyDelete
  5. Все просто и понятно (это относится и к другим постам автора). Спасибо. Думаю, именно то, что нужно для въезжающих в NHibernate. Буду регулярно следить за этим блогом.

    ReplyDelete
  6. Привет!
    criteria.CreateCriteria("Orders") .Add

    (Restrictions.Like("Customer",

    requiredCustomerName, MatchMode.Anywhere));
    вот тут, собственно, ни фига не понятно.
    Что происходит с объектом после CreateCriteria?

    Все дальнейшие Add обращаются уже не к полям

    исходного класса (Product), а к указанному

    CreateCriteria подклассу (Order)? А как вернуться

    на уровень вверх, если после этого надо ещё

    добавить одно условие для исходного класса

    (Product)?

    ReplyDelete
  7. Выше возвращаться просто не надо. Если есть необходимость добавить доп. параметры к запросу по самому классу Product, то их можно добавить просто перед вызовом CreateCriteria("Orders")

    ReplyDelete
  8. Класс. Запросы - супер.

    ReplyDelete
  9. Ссылку пофиксите

    ReplyDelete