Мы можем управлять порядком выражения LINQ с Пропуском (), Взять () и OrderBy ()

Я использую 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
10
задан Robert Koritnik 16 September 2010 в 19:21
поделиться

6 ответов

Мое временное решение

Мне удалось обойти эту проблему. Не поймите меня неправильно. Я еще не решил проблему с приоритетом, но смягчил ее.

Что я сделал?

Это код, который я использовал, пока не получил ответ от 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 ...

Надеюсь, кто-нибудь найдет это решение полезный.

4
ответ дан 4 December 2019 в 03:16
поделиться

Я не работал непосредственно с Linq to Entities, но он должен иметь возможность подключать определенные хранимые процедуры к определенным местам, когда это необходимо. (Если это так, вы можете превратить этот запрос в хранимую процедуру, выполняющую именно то, что требуется, и делающую это эффективно.

2
ответ дан 4 December 2019 в 03:16
поделиться

Вы абсолютно уверены, что заказ отключен? Как выглядит 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.

1
ответ дан 4 December 2019 в 03:16
поделиться

Исходя из вашего комментария, сохранение значений в списке неприемлемо:

Нет способа полностью минимизировать итерации, как вы предполагали (и как я бы тоже попытался, живя надеждой). Сократить количество итераций на одну было бы неплохо. Возможно ли просто получить граф один раз и сохранить его в кэше/сессии? Тогда вы могли бы:

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 или избежать необходимости получать его каждый раз. Даже если нет, это лучшее, что вы можете сделать.

0
ответ дан 4 December 2019 в 03:16
поделиться

Один способ:

var query = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).ToList();
int total = query.Count;
var result = query.Skip(n).Take(x).ToList();

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

-1
ответ дан 4 December 2019 в 03:16
поделиться

Не могли бы вы создать образец, иллюстрирующий проблему, и отправить его нам (support * devart * com, тема "EF: Skip, Take, OrderBy") ?
Надеюсь, мы сможем вам помочь.
Вы также можете связаться с нами, используя наш форум или контактную форму .

0
ответ дан 4 December 2019 в 03:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: