Можем ли мы контролировать порядок выражений LINQ с помощью Skip(), Take() и OrderBy()?
-
21-09-2019 - |
Вопрос
Я использую LINQ to Entities для отображения постраничных результатов.Но у меня возникли проблемы с комбинацией 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
Решение
Мое обходное решение
Мне удалось обойти эту проблему.Не поймите меня неправильно.Я еще не решил проблему приоритета, но смягчил ее.
Что я сделал?
Это код, который я использовал, пока не получу ответ от Деварт.Если они не смогут решить эту проблему, мне в конце концов придется использовать этот код.
// 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, но у него должна быть возможность при необходимости подключать определенные хранимые процедуры к определенным местам.(Linq to SQL сделал это.) Если да, то вы можете превратить этот запрос в хранимую процедуру, выполняющую именно то, что требуется, и делая это эффективно.
Вы абсолютно уверены, что заказ отменен?Как выглядит 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();
Надеюсь, вы сможете каким-то образом кэшировать счетчик или избежать его необходимости каждый раз.Даже если вы не можете, это лучшее, что вы можете сделать.
Не могли бы вы создать образец, иллюстрирующий проблему, и отправить его нам (поддержка *devart*com, тема «EF:Пропустить, взять, заказать»)?
Надеюсь, мы сможем вам помочь.
Вы также можете связаться с нами, используя наш форумы или Форма обратной связи.
В одну сторону:
var query = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).ToList();
int total = query.Count;
var result = query.Skip(n).Take(x).ToList();
Прежде чем пропустить, преобразуйте его в список.Это не слишком эффективно, заметьте...