skuteczny sposób wdrożenia stronicowania

Czy powinienem użyć metody LINQ Skip() i Take() do stronicowania, czy zaimplementować własne stronicowanie za pomocą zapytania SQL?

Który jest najskuteczniejszy? Dlaczego miałbym wybierać jedno nad drugim?

Używam SQL Server 2008, ASP.NET MVC i LINQ.

Author: Lukas Eder, 2009-02-14

9 answers

Próbując dać ci krótką odpowiedź na twoje wątpliwości, jeśli wykonasz metody {[3] } na linq (z SQL 2005 / 2008 jako serwer bazy danych) twoje zapytanie będzie używać instrukcji Select ROW_NUMBER() Over ..., z jakimś bezpośrednim stronicowaniem w silniku SQL.

Podając przykład, mam tabelę db o nazwie mtcity i napisałem następujące zapytanie (działa również z LINQ do encji):

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

Zapytanie wynikowe będzie brzmiało:

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]

Czyli dostęp do danych z oknem (całkiem fajne, btw bo będzie zwracanie danych od samego początku i dostęp do tabeli, o ile spełnione są warunki). Będzie to bardzo podobne do:

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

Z tym wyjątkiem, że to drugie zapytanie będzie wykonywane szybciej niż wynik linq, ponieważ będzie używać wyłącznie indeksu do tworzenia okna dostępu do danych; oznacza to, że jeśli potrzebujesz filtrowania, filtrowanie powinno być (lub musi być) w liście encji (gdzie wiersz jest tworzony) i niektóre indeksy powinny być również tworzone, aby nadążyć dobre wykonanie.

Co jest lepsze?

Jeśli masz dość solidny przepływ pracy w swojej logice, implementacja właściwego sposobu SQL będzie skomplikowana. W takim przypadku rozwiązaniem będzie LINQ.

Jeśli możesz obniżyć tę część logiki bezpośrednio do SQL( w procedurze składowanej), będzie to jeszcze lepsze, ponieważ możesz zaimplementować drugie zapytanie, które Ci pokazałem (używając indeksów) i pozwolić SQL na wygenerowanie i zapisanie planu wykonania zapytania (poprawa wydajności).

 170
Author: rodrigoelp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-09 12:10:02

Spróbuj użyć

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

Aby uzyskać wiersze od 501 do 600 W Sql serverze, bez ładowania ich do pamięci. Zwróć uwagę, że ta składnia stała się dostępna tylko w SQL Server 2012

 47
Author: d.popov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-16 08:42:46

Podczas gdy LINQ-to-SQL wygeneruje klauzulę OFFSET (ewentualnie emulowaną za pomocą ROW_NUMBER() OVER() jak inni wspominali ), istnieje zupełnie inny, znacznie szybszy sposób wykonywania stronicowania w SQL. Jest to często nazywane "seek method", jak opisano w ten wpis na blogu tutaj .

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

Wartości @previousScore i @previousPlayerId są odpowiednimi wartościami ostatniego rekordu z poprzedniej strony. Pozwala to na pobranie "następnej" strony. Jeśli kierunek ORDER BY wynosi ASC, po prostu użyj > zamiast tego.

Przy użyciu powyższej metody, nie można od razu przejść do strony 4 bez pobierania poprzednich 40 rekordów. Ale często i tak nie chcesz skakać tak daleko. Zamiast tego otrzymujesz znacznie szybsze zapytanie, które może być w stanie pobrać dane w stałym czasie, w zależności od indeksowania. Ponadto Twoje strony pozostają "stabilne", bez względu na to, czy Podstawowe Dane ulegną zmianie (np. na stronie 1, podczas gdy ty jesteś na stronie 4).

Jest to najlepszy sposób na zaimplementowanie stronicowania podczas leniwego ładowania więcej danych w aplikacjach internetowych, na przykład.

Uwaga, metoda "seek" jest również nazywana stronicowaniem keyset .

 10
Author: Lukas Eder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:33

LinqToSql automatycznie skonwertuje a .Skip (N1).Weź(N2) do składni TSQL dla Ciebie. W rzeczywistości każde "zapytanie", które robisz w Linq, jest tak naprawdę tylko tworzeniem zapytania SQL dla Ciebie w tle. Aby to przetestować, po prostu uruchom SQL Profiler, gdy aplikacja jest uruchomiona.

Metodologia skip/take działa bardzo dobrze dla mnie, i innych z tego, co czytałem.

Z ciekawości, jaki masz rodzaj samo-stronicowania zapytań, który Twoim zdaniem jest bardziej wydajny niż Linq ' s skip / take?

 5
Author: mandreko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-14 04:51:18

Używamy CTE zawiniętego w dynamiczny SQL (ponieważ nasza aplikacja wymaga dynamicznego sortowania danych po stronie serwera) w ramach procedury składowanej. Mogę podać podstawowy przykład, jeśli chcesz.

Nie miałem okazji przyjrzeć się T/SQL, który tworzy LINQ. Czy ktoś może zamieścić próbkę?

Nie używamy LINQ ani prostego dostępu do tabel, ponieważ wymagamy dodatkowej warstwy bezpieczeństwa(z uwagi na dynamiczny SQL łamie to nieco).

Coś takiego powinno zadziałać. Ty może dodawać parametryzowane wartości parametrów, itd.
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'
 4
Author: mrdenny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-13 05:54:49

W 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]

W t0 są wszystkie rekordy W t1 są tylko te, które odpowiadają tej stronie

 1
Author: ch2o,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-20 00:55:55

Można jeszcze poprawić wydajność, chech to

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

Jeśli użyjesz W ten sposób from, Da to lepszy wynik:

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

Powód: ponieważ używasz klasy where w tabeli City, która wyeliminuje wiele rekordów przed przystąpieniem do MtCity, więc na 100% pewno zwiększy to wydajność wielokrotnie...

W każdym razie odpowiedź rodrigoelpa jest naprawdę pomocna.

Dzięki

 0
Author: Ali Adravi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-09 07:47:33

Możesz zaimplementować stronicowanie w ten prosty sposób, przekazując 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
Author: Rae Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-20 08:45:16

W 2008 roku nie możemy użyć Skip ().Take ()

Droga jest:

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
Author: Belen Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-19 07:06:33