Извлечение пагинации с коллекцией

Рассмотрим две сущности Person который имеет one-to-many коллекция Vehicles

public class Person
{
    public IList<Vehicle> Vehicles { get; set;}
}

public class Vehicle 
{
   public string Name { get; set;}

   public Person Owner { get; set; }
}

Я отображаю сетку лиц, имеющих транспортное средство, и показываю имя первого транспортного средства в сетке. Сетка разбита на страницы. Я использую следующие критерии для извлечения данных

У меня есть критерии для загрузки данных для вида сетки как

var criteria = DetachedCriteria.For<Person>()
    .CreateAlias("Vehicles","vehicle", JoinType.InnerJoin)
    .SetResultTransformer(new DistinctRootEntityResultTransformer())
    .SetMaxResults(pageSize)
    .SetFirstResult((page - 1) * pageSize)

criteria.Add(Restrictions.Eq("vehicle.Name", "super"));

где page а также pageSize рассчитываются биты.

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

Есть ли способ решить эту проблему?

1 ответ

Решение

Этот вид запросов должен всегда использовать subquery вместо любого типа JOIN. Это также означает, что элемент коллекционирования имеет ссылку на родителя (как в нашем случае).

Итак, здесь мы создаем внутренний выбор для Vehicle:

var vehicles = DetachedCriteria.For<Vehicle>();
// add any amount or kind of WHERE parts
vehicles.Add(Restrictions.Eq("vehicle.Name", "super"))
// and essential SELECT Person ID
vehicles.SetProjection( Projections.Property("Owner.ID"));

Теперь мы можем настроить вышеупомянутый запрос, чтобы он работал только на уровне root/parent:

var criteria = DetachedCriteria.For<Person>()

// instead of this
// .CreateAlias("Vehicles","vehicle", JoinType.InnerJoin)

// we will use subquery
.Add(Subqueries.PropertyIn("ID", vehicles));

// Wrong to use this approach at all
//.SetResultTransformer(new DistinctRootEntityResultTransformer())

.SetMaxResults(pageSize)
.SetFirstResult((page - 1) * pageSize)

Это создаст SELECT следующим образом:

SELECT p....
FROM Person AS p
WHERE p.ID IN (
  SELECT v.OwnerId
  FROM Vehcile AS v
    WHERE v.Name = 'super' ...
)

Смотрите также:

И как получить коллекцию Vehicles (до сих пор просто используется для фильтрации)? Лучший (если не единственный) способ - использовать операторы SELECT 1 + 1. Простое и встроенное решение batch-size установка. Просто отметьте коллекцию Vehicles с этой настройкой (например, batch-size="25") и с помощью еще нескольких операторов SELECT все данные будут эффективно загружены. Увидеть:

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