Я использую LINQ для Объектов для отображения разбитых на страницы результатов. Но у меня есть проблемы с комбинацией Skip()
, Take()
и OrderBy()
вызовы.
Все хорошо работает, за исключением того, что OrderBy()
присвоен слишком поздно. Это выполняется после того, как набор результатов был сокращен Skip()
и Take()
.
Таким образом, каждая страница результатов имеет объекты в порядке. Но упорядочивание сделано на небольшом количестве страницы данных вместо того, чтобы заказать полного набора и затем ограничить те записи с Skip()
и Take()
.
Как я устанавливаю приоритет с этими операторами?
var query = ctx.EntitySet.Where(/* filter */).OrderByDescending(e => e.ChangedDate);
int total = query.Count();
var result = query.Skip(n).Take(x).ToList();
Одно возможное решение состояло бы в том, чтобы применить кластерный индекс для упорядочивания столбцом, но этот столбец часто изменяется, который замедлил бы производительность базы данных на вставках и обновлениях. И я действительно не хочу делать это.
Я работал ToTraceString()
на моем запросе, где мы можем на самом деле видеть, когда порядок применяется к набору результатов. К сожалению, в конце.:(
SELECT
-- columns
FROM (SELECT
-- columns
FROM (SELECT -- columns
FROM ( SELECT
-- columns
FROM table1 AS Extent1
WHERE EXISTS (SELECT
-- single constant column
FROM table2 AS Extent2
WHERE (Extent1.ID = Extent2.ID) AND (Extent2.userId = :p__linq__4)
)
) AS Project2
limit 0,10 ) AS Limit1
LEFT OUTER JOIN (SELECT
-- columns
FROM table2 AS Extent3 ) AS Project3 ON Limit1.ID = Project3.ID
UNION ALL
SELECT
-- columns
FROM (SELECT -- columns
FROM ( SELECT
-- columns
FROM table1 AS Extent4
WHERE EXISTS (SELECT
-- single constant column
FROM table2 AS Extent5
WHERE (Extent4.ID = Extent5.ID) AND (Extent5.userId = :p__linq__4)
)
) AS Project6
limit 0,10 ) AS Limit2
INNER JOIN table3 AS Extent6 ON Limit2.ID = Extent6.ID) AS UnionAll1
ORDER BY UnionAll1.ChangedDate DESC, UnionAll1.ID ASC, UnionAll1.C1 ASC
Мне удалось обойти эту проблему. Не поймите меня неправильно. Я еще не решил проблему с приоритетом, но смягчил ее.
Что я сделал?
Это код, который я использовал, пока не получил ответ от Devart . Если они не смогут решить эту проблему, мне в конце концов придется использовать этот код.
// get ordered list of IDs
List<int> ids = ctx.MyEntitySet
.Include(/* Related entity set that is needed in where clause */)
.Where(/* filter */)
.OrderByDescending(e => e.ChangedDate)
.Select(e => e.Id)
.ToList();
// get total count
int total = ids.Count;
if (total > 0)
{
// get a single page of results
List<MyEntity> result = ctx.MyEntitySet
.Include(/* related entity set (as described above) */)
.Include(/* additional entity set that's neede in end results */)
.Where(string.Format("it.Id in {{{0}}}", string.Join(",", ids.ConvertAll(id => id.ToString()).Skip(pageSize * currentPageIndex).Take(pageSize).ToArray())))
.OrderByDescending(e => e.ChangedOn)
.ToList();
}
Прежде всего я получаю упорядоченные идентификаторы своих сущностей. Получение только идентификаторов хорошо работает даже с большим набором данных. MySql-запрос довольно прост и работает очень хорошо. Во второй части я разделяю эти идентификаторы и использую их для получения реальных экземпляров сущностей.
Если подумать, это должно работать даже лучше, чем то, как я делал это вначале (как описано в моем вопросе), потому что получить общий счет намного быстрее из-за упрощенного запроса. Вторая часть практически очень похожа, за исключением того, что мои объекты возвращаются скорее по их идентификаторам, а не по разделам с использованием Skip
и Take
...
Надеюсь, кто-нибудь найдет это решение полезный.
Я не работал непосредственно с Linq to Entities, но он должен иметь возможность подключать определенные хранимые процедуры к определенным местам, когда это необходимо. (Если это так, вы можете превратить этот запрос в хранимую процедуру, выполняющую именно то, что требуется, и делающую это эффективно.
Вы абсолютно уверены, что заказ отключен? Как выглядит SQL?
Можете ли вы изменить порядок вашего кода и опубликовать результат?
// Redefine your queries.
var query = ctx.EntitySet.Where(/* filter */).OrderBy(e => e.ChangedDate);
var skipped = query.Skip(n).Take(x);
// let's look at the SQL, shall we?
var querySQL = query.ToTraceString();
var skippedSQL = skipped.ToTraceString();
// actual execution of the queries...
int total = query.Count();
var result = skipped.ToList();
Изменить:
Я абсолютно уверен. Вы можете проверить мое «редактирование», чтобы увидеть результат трассировки моего запроса с пропущенным результатом трассировки, что в данном случае обязательно. Счетчик на самом деле не важен.
Да, я это вижу. Вау, это загвоздка. Может даже быть явной ошибкой. Замечу, что вы не используете SQL Server ... какую БД вы используете? Похоже, это MySQl.
Исходя из вашего комментария, сохранение значений в списке неприемлемо:
Нет способа полностью минимизировать итерации, как вы предполагали (и как я бы тоже попытался, живя надеждой). Сократить количество итераций на одну было бы неплохо. Возможно ли просто получить граф один раз и сохранить его в кэше/сессии? Тогда вы могли бы:
int total = ctx.EntitySet.Count; // Hopefully you can not repeat doing this.
var result = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).Skip(n).Take(x).ToList();
Надеюсь, вы сможете как-то кэшировать Count или избежать необходимости получать его каждый раз. Даже если нет, это лучшее, что вы можете сделать.
Один способ:
var query = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).ToList();
int total = query.Count;
var result = query.Skip(n).Take(x).ToList();
Преобразуйте его в список перед пропуском. Это не слишком эффективно, заметьте ...
Не могли бы вы создать образец, иллюстрирующий проблему, и отправить его нам (support * devart * com, тема "EF: Skip, Take, OrderBy") ?
Надеюсь, мы сможем вам помочь.
Вы также можете связаться с нами, используя наш форум или контактную форму .