Можем ли мы контролировать порядок выражений LINQ с помощью Skip(), Take() и OrderBy()?

StackOverflow https://stackoverflow.com/questions/2493900

Вопрос

Я использую 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();

Прежде чем пропустить, преобразуйте его в список.Это не слишком эффективно, заметьте...

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top