2009-02-14 7 views
111

Devo usare il metodo Skip() di LINQ e Take() per il paging o implementare il mio paging con una query SQL?modo efficiente per implementare il paging

che è il più efficace? Perché dovrei sceglierne uno rispetto all'altro?

sto usando SQL Server 2008, ASP.NET MVC e LINQ.

+0

Penso che dipenda. Su cosa stai lavorando? che tipo di carico avrà? – BuddyJoe

+0

Dai un'occhiata anche a questa risposta: http: // StackOverflow.it/a/10639172/416996 –

+0

Dai un'occhiata anche a http://www.aspsnippets.com/Articles/Custom-Paging-in-ASP.Net-GridView-using-SQL-Server-Stored-Procedure.aspx –

risposta

169

Cercando di dare una breve risposta al vostro dubbio, se si esegue i metodi skip(n).take(m) su LINQ (con SQL 2005/2008 come server di database) la vostra richiesta sarà sta usando l'istruzione Select ROW_NUMBER() Over ..., con è in qualche modo il paging diretto nel motore SQL.

Dare un esempio, ho una tabella db chiamato mtcity e ho scritto la seguente query (lavoro così con LINQ to entità):

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

La query risultante sarà:

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] 

Quale è un accesso ai dati con finestra (piuttosto interessante, btw cuz restituirà i dati sin dall'inizio e accederà alla tabella finché le condizioni saranno soddisfatte). Questo sarà molto simile a:

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 

Con l'eccezione che, questa seconda query viene eseguita più veloce del risultato LINQ perché sarà utilizzato esclusivamente l'indice per creare la finestra di accesso ai dati; questo significa che se hai bisogno di un filtro, il filtro deve essere (o deve essere) nell'elenco Entity (dove viene creata la riga) e alcuni indici dovrebbero essere creati per mantenere le buone prestazioni.

Ora, che cosa è meglio?

Se si dispone di un solido flusso di lavoro nella logica, l'implementazione del corretto modo SQL sarà complicata. In tal caso LINQ sarà la soluzione.

Se è possibile abbassare quella parte della logica direttamente su SQL (in una stored procedure), sarà ancora meglio perché è possibile implementare la seconda query che ho mostrato (utilizzando gli indici) e consentire a SQL di generare e archiviare la parte Piano di esecuzione della query (miglioramento delle prestazioni).

+2

Risposta piacevole: un'espressione di tabella comune è un buon modo per eseguire il paging. –

+0

Puoi controllare la mia domanda (http://stackoverflow.com/questions/11100929/asp-net-mvc-webgrid-efficent-paging/11104822#comment14552347_11104822)? Ho creato un SP che ho aggiunto al mio EDMX e l'ho usato in una query linq-to-entities. – Misi

+2

+1, buona risposta, apprezzo che tu spieghi i vantaggi in termini di prestazioni del secondo esempio – Cohen

5

LinqToSql convertirà automaticamente un .Skip (N1) .Take (N2) nella sintassi TSQL per te. In effetti, ogni "query" che fai in Linq, in realtà sta solo creando una query SQL per te in background. Per testare questo, basta eseguire SQL Profiler mentre l'applicazione è in esecuzione.

Lo skip/prendere metodologia ha funzionato molto bene per me, e gli altri da quello che ho letto.

Per curiosità, quale tipo di query di ricerca automatica si dispone, che si ritiene sia più efficiente di Salta/Take di Linq?

4

Usiamo un CTE avvolto in SQL dinamico (perché la nostra applicazione richiede ordinamento dinamica dei dati lato server) all'interno di una stored procedure. Posso fornire un esempio di base, se lo desideri.

non ho avuto la possibilità di guardare il T/SQL che LINQ produce. Qualcuno può pubblicare un campione?

Non usiamo LINQ o accesso diretto alle tabelle poiché richiediamo un ulteriore livello di sicurezza (a condizione che l'SQL dinamico lo interrompa in qualche modo).

Qualcosa del genere dovrebbe fare il trucco. È possibile aggiungere valori parametrizzati per i parametri, ecc

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

@ mrdenny - Un ** suggerimento per l'esempio ** che hai fornito: Con 'sp_executesql' hai la possibilità di passare parametri in modo sicuro, es .:' EXECUTE sp_executesql 'WITH myCTE AS ... WHERE Col4 = @ p1) ... ',' @ p1 nvarchar (max) ', @ ValueForCol4'. Sicuro in questo contesto significa che è robusto contro l'iniezione SQL - puoi passare ogni possibile valore all'interno della variabile '@ ValueForCol4' - anche' '-' ', e la query funzionerà ancora! – Matt

+1

@mrdenny Ciao, invece di concatenare la query usiamo qualcosa di simile: 'SELEZIONA ROW_NUMBER() OVER (ORDER BY CASO QUANDO @CampoId = 1 THEN Id QUANDO @CampoId = 2 allora field2 END)' – Ezequiel

+0

in grado di produrre alcuni terribili piani di esecuzione SQL. – mrdenny

0

è possibile migliorare ulteriormente le prestazioni, chech questo

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 

se si vuole utilizzare il da in questo modo si darà un risultato migliore:

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

ragione: perché si utilizza la cui classe sulla tabella CityEntities che eliminerà molti record prima di entrare in MtCity, quindi 100% sicuro aumenterà le prestazioni molte volte ...

In ogni caso, risposta di rodrigoelp i è davvero utile

Grazie

+0

Dubito che ci sarà un impatto sulle prestazioni usando questo consiglio. Impossibile trovare un riferimento per questo ma l'ordine interno di join in query può essere diverso dall'effettivo ordine di join. Quest'ultimo viene deciso da Query Optimizer utilizzando le statistiche della tabella e le stime dei costi operativi. –

+0

@ImreP: questo potrebbe in qualche modo corrispondere al [metodo di ricerca, che ho descritto] (http://stackoverflow.com/a/19609720/521799). Anche se, non sono sicuro di dove '@ p0' e più specificamente' @ p1' viene da –

47

Try usando

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

per ottenere le righe da 501 a 600 nel server SQL, senza caricarli in memoria. Si noti che questa sintassi è diventato disponibile con SQL Server 2012 solo

+0

penso che questo non sia corretto. L'SQL visualizzato mostra le righe da 502 a 601 (a meno che tu non stia indicizzando a zero?) – Smudge202

10

Mentre LINQ to SQL genererà una clausola OFFSET (possibilmente emulato usando ROW_NUMBER() OVER()as others have mentioned), v'è un modo completamente diverso molto più veloce per eseguire il paging in SQL. Questo è spesso chiamato il "metodo di ricerca" come descritto in this blog post here.

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 

I @previousScore e @previousPlayerId valori sono i rispettivi valori dell'ultimo record dalla pagina precedente. Questo ti permette di recuperare la pagina "successiva". Se la direzione ORDER BY è ASC, utilizzare invece >.

Con il metodo di cui sopra, non si può passare immediatamente alla pagina 4 senza aver prima recuperato i precedenti 40 record. Ma spesso, non vuoi saltare così tanto in ogni caso. Invece, si ottiene una query molto più veloce che potrebbe essere in grado di recuperare i dati in tempo costante, a seconda dell'indicizzazione. Inoltre, le tue pagine rimangono "stabili", indipendentemente dal fatto che i dati sottostanti cambino (ad es. A pagina 1, mentre sei a pagina 4).

Questo è il modo migliore per attuare paging quando lazy loading ulteriori dati in applicazioni Web, per esempio.

nota, il "metodo seek" è anche chiamato keyset paging.

1

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

In t0 sono tutti i record In t1 sono solo quelli corrispondenti a quella pagina

0

È possibile implementare il paging in questo modo semplice passando 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