эффективный способ реализации пейджинга - PullRequest
114 голосов
/ 14 февраля 2009

Должен ли я использовать методы LINQ Skip() и Take() для пейджинга или реализовать свой собственный пейджинг с помощью запроса SQL?

Что наиболее эффективно? Почему я бы выбрал одно из другого?

Я использую SQL Server 2008, ASP.NET MVC и LINQ.

Ответы [ 9 ]

170 голосов
/ 18 марта 2009

Попытка дать вам краткий ответ на ваши сомнения, если вы выполняете методы skip(n).take(m) в linq (с SQL 2005/2008 в качестве сервера базы данных), ваш запрос будет использовать оператор Select ROW_NUMBER() Over ..., с каким-то образом прямой подкачкой страниц в движке SQL.

Приведу пример. У меня есть таблица базы данных с именем mtcity, и я написал следующий запрос (также работает с linq для сущностей):

using (DataClasses1DataContext c = new DataClasses1DataContext())
{
    var query = (from MtCity2 c1 in c.MtCity2s
                select c1).Skip(3).Take(3);
    //Doing something with the query.
}

Результирующий запрос будет:

SELECT [t1].[CodCity], 
    [t1].[CodCountry], 
    [t1].[CodRegion], 
    [t1].[Name],  
    [t1].[Code]
FROM (
    SELECT ROW_NUMBER() OVER (
        ORDER BY [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]) AS [ROW_NUMBER], 
        [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
    FROM [dbo].[MtCity] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]

Что такое оконный доступ к данным (довольно круто, кстати, потому что они будут возвращать данные с самого начала и будут обращаться к таблице, пока выполняются условия). Это будет очень похоже на:

With CityEntities As 
(
    Select ROW_NUMBER() Over (Order By CodCity) As Row,
        CodCity //here is only accessed by the Index as CodCity is the primary
    From dbo.mtcity
)
Select [t0].[CodCity], 
        [t0].[CodCountry], 
        [t0].[CodRegion], 
        [t0].[Name],
        [t0].[Code]
From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

За исключением того, что этот второй запрос будет выполнен быстрее, чем результат linq, поскольку он будет использовать исключительно индекс для создания окна доступа к данным; это означает, что если вам нужна некоторая фильтрация, фильтрация должна быть (или должна быть) в списке сущностей (где создается строка), а также должны быть созданы некоторые индексы для поддержания хорошей производительности.

Теперь, что лучше?

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

Если вы можете понизить эту часть логики непосредственно до SQL (в хранимой процедуре), это будет еще лучше, потому что вы можете реализовать второй запрос, который я вам показал (с помощью индексов), и позволить SQL генерировать и хранить выполнение План запроса (повышение производительности).

48 голосов
/ 21 декабря 2012

Попробуйте использовать

FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY

чтобы получить строки от 501 до 600 на сервере SQL, не загружая их в память. Обратите внимание, что этот синтаксис стал доступен с SQL Server 2012 только

10 голосов
/ 26 октября 2013

Хотя LINQ-to-SQL сгенерирует предложение OFFSET (возможно, эмулируемое с использованием ROW_NUMBER() OVER() , как другие упоминали ), существует совершенно другой, гораздо более быстрый способ выполнения подкачки в SQL. Это часто называют «методом поиска», как описано в этом сообщении здесь .

SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < @previousScore)
   OR (score = @previousScore AND player_id < @previousPlayerId)
ORDER BY score DESC, player_id DESC

Значения @previousScore и @previousPlayerId являются соответствующими значениями последней записи с предыдущей страницы. Это позволяет вам получить «следующую» страницу. Если направление ORDER BY равно ASC, просто используйте вместо него >.

С помощью описанного выше метода вы не можете сразу перейти к странице 4, предварительно не загрузив предыдущие 40 записей. Но часто, вы все равно не хотите прыгать так далеко. Вместо этого вы получаете гораздо более быстрый запрос, который может извлекать данные в постоянное время, в зависимости от вашей индексации. Кроме того, ваши страницы остаются «стабильными», независимо от того, изменяются ли базовые данные (например, на странице 1, пока вы находитесь на странице 4).

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

Обратите внимание, что "метод поиска" также называется подкачки с клавиатуры .

5 голосов
/ 14 февраля 2009

LinqToSql автоматически преобразует .Skip (N1) .Take (N2) в синтаксис TSQL для вас. Фактически, каждый «запрос», который вы делаете в Linq, на самом деле просто создает для вас SQL-запрос в фоновом режиме. Чтобы проверить это, просто запустите SQL Profiler во время работы приложения.

Методика пропуска / забора очень хорошо работает для меня и других из того, что я прочитал.

Из любопытства, какой тип самопейджингового запроса у вас есть, который, по вашему мнению, более эффективен, чем пропуск Linq?

4 голосов
/ 14 февраля 2009

Мы используем CTE, завернутый в динамический SQL (потому что наше приложение требует динамической сортировки на стороне сервера данных) в рамках хранимой процедуры. Я могу привести базовый пример, если хотите.

У меня не было возможности взглянуть на T / SQL, который производит LINQ. Может кто-нибудь выложить образец?

Мы не используем LINQ или прямой доступ к таблицам, поскольку нам требуется дополнительный уровень безопасности (если динамический SQL несколько нарушает это).

Нечто подобное должно сработать. Вы можете добавить в параметризованные значения параметры и т. Д.

exec sp_executesql 'WITH MyCTE AS (
    SELECT TOP (10) ROW_NUMBER () OVER ' + @SortingColumn + ' as RowID, Col1, Col2
    FROM MyTable
    WHERE Col4 = ''Something''
)
SELECT *
FROM MyCTE
WHERE RowID BETWEEN 10 and 20'
2 голосов
/ 20 марта 2014

В SQL Server 2008:

DECLARE @PAGE INTEGER = 2
DECLARE @TAKE INTEGER = 50

SELECT [t1].*
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[COLUMNORDER] DESC) AS [ROW_NUMBER], [t0].*
    FROM [dbo].[TABLA] AS [t0]
    WHERE ([t0].[COLUMNS_CONDITIONS] = 1)
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN ((@PAGE*@TAKE) - (@TAKE-1)) AND (@PAGE*@TAKE)
ORDER BY [t1].[ROW_NUMBER]

В t0 все записи В t1 только те, которые соответствуют этой странице

0 голосов
/ 19 апреля 2018

В 2008 году мы не можем использовать Skip (). Take ()

Путь:

var MinPageRank = (PageNumber - 1) * NumInPage + 1
var MaxPageRank = PageNumber * NumInPage

var visit = Visita.FromSql($"SELECT * FROM (SELECT [RANK] = ROW_NUMBER() OVER (ORDER BY Hora DESC),* FROM Visita WHERE ) A WHERE A.[RANK] BETWEEN {MinPageRank} AND {MaxPageRank}").ToList();
0 голосов
/ 31 января 2017

Вы можете реализовать пейджинг таким простым способом, передав PageIndex

Declare @PageIndex INT = 1
Declare  @PageSize INT = 20

Select ROW_NUMBER() OVER ( ORDER BY Products.Name ASC )  AS RowNumber,
    Products.ID,
    Products.Name
into #Result 
From Products

SELECT @RecordCount = COUNT(*) FROM #Results 

SELECT * 
FROM #Results
WHERE RowNumber
BETWEEN
    (@PageIndex -1) * @PageSize + 1 
    AND
    (((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1
0 голосов
/ 09 февраля 2012

вы можете улучшить производительность, чеч это

From CityEntities c
Inner Join dbo.MtCity t0 on c.CodCity = t0.CodCity
Where c.Row Between @p0 + 1 AND @p0 + @p1
Order By c.Row Asc

, если вы будете использовать метод from таким образом, это даст лучший результат:

From   dbo.MtCity  t0
   Inner Join  CityEntities c on c.CodCity = t0.CodCity

причина: потому что вы используете класс where в таблице CityEntities, который удалит много записей перед присоединением к MtCity, поэтому на 100% уверен, что это увеличит производительность во много раз ...

В любом случае ответ Родригоэлпа действительно полезен.

Спасибо

...