Можем ли мы контролировать порядок выражений LINQ с помощью Skip (), Take () и OrderBy () - PullRequest
10 голосов
/ 22 марта 2010

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

Одно возможное (но плохое) решение

Одним из возможных решений было бы применение кластерного индекса к порядку по столбцу, но этот столбец часто меняется, что снижает производительность базы данных при вставках и обновлениях. И я действительно не хочу этого делать.

EDIT

Я выполнил ToTraceString() в моем запросе, где мы можем увидеть, когда order by применяется к результирующему набору. К сожалению в конце. (

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

Ответы [ 6 ]

4 голосов
/ 24 марта 2010

Мой обходной раствор

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

Что я сделал?

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

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

2 голосов
/ 22 марта 2010

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

1 голос
/ 22 марта 2010

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

Edit:

Я абсолютно уверен. Вы можете проверить мое "редактирование", чтобы увидеть результат трассировки моего запроса с пропущенным результатом трассировки, который является обязательным в этом случае Граф не очень важен.

Да, я вижу это. Вау, это тупик. Может даже быть прямой ошибкой. Я заметил, что вы не используете SQL Server ... какую БД вы используете? Похоже, это может быть MySQl.

0 голосов
/ 24 марта 2010

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

0 голосов
/ 22 марта 2010

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

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

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 голосов
/ 22 марта 2010

В одну сторону:

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

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

...