2009-02-14 18 views
111

¿Debo usar el método Skip() y Take() de LINQ para paginación, o implementar mi propia paginación con una consulta SQL?manera eficiente de implementar paginación

¿Cuál es el más eficiente? ¿Por qué elegiría uno sobre el otro?

Estoy usando SQL Server 2008, ASP.NET MVC y LINQ.

+0

Creo que depende. ¿En qué aplicación puedes trabajar? ¿Qué tipo de carga tendrá? – BuddyJoe

+0

Mire también esta respuesta: http: // stackoverflow.com/a/10639172/416996 –

+0

Eche un vistazo a esto también http://www.aspsnippets.com/Articles/Custom-Paging-in-ASP.Net-GridView-using-SQL-Server-Stored-Procedure.aspx –

Respuesta

169

Tratando de dar una breve respuesta a su duda, si ejecuta los métodos skip(n).take(m) en LINQ (con SQL 2005/2008 como servidor de base de datos) la consulta será estar utilizando la instrucción Select ROW_NUMBER() Over ..., de alguna manera es una paginación directa en el motor SQL.

Dándole un ejemplo, tengo una tabla db llama mtcity y escribí la siguiente consulta (trabajo, así como con LINQ a las entidades):

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 consulta resultante será:

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] 

que es un acceso de datos en ventana (muy bien, por cierto, devolverá datos desde el principio y accederá a la tabla siempre que se cumplan las condiciones). Esto será muy similar 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 la excepción de que, esta segunda consulta se ejecuta más rápido que el resultado de LINQ, ya que va a utilizar exclusivamente el índice para crear la ventana de acceso a los datos; esto significa que, si necesita algún tipo de filtrado, el filtro debe estar (o debe estar) en la lista de Entidades (donde se crea la fila) y también se deben crear algunos índices para mantener el buen rendimiento.

Ahora, qué es mejor?

Si tiene un flujo de trabajo bastante sólido en su lógica, implementar la forma SQL correcta será complicado. En ese caso, LINQ será la solución.

Si puede bajar esa parte de la lógica directamente a SQL (en un procedimiento almacenado), será aún mejor porque puede implementar la segunda consulta que le mostré (usando índices) y permitir que SQL genere y almacene el Plan de ejecución de la consulta (mejora del rendimiento).

+2

Buena respuesta: la expresión de tabla común es una buena manera de hacer paginación. –

+0

¿Podría verificar mi pregunta (http://stackoverflow.com/questions/11100929/asp-net-mvc-webgrid-efficent-paging/11104822#comment14552347_11104822)? Hice un SP que agregué a mi EDMX y lo usé en una consulta de linq a entidades. – Misi

+2

+1, buena respuesta, le agradezco que explique los beneficios de rendimiento del segundo ejemplo – Cohen

5

LinqToSql convertirá automáticamente un .Skip (N1) .Take (N2) en la sintaxis de TSQL por usted. De hecho, cada "consulta" que haces en Linq, en realidad solo está creando una consulta SQL para ti en el fondo. Para probar esto, simplemente ejecute el Analizador de SQL mientras se ejecuta su aplicación.

La metodología omitir/tomar ha funcionado muy bien para mí, y otras por lo que he leído.

Por curiosidad, ¿qué tipo de consulta de autopaginación tiene, que cree que es más eficiente que la omisión/toma de Linq?

4

Utilizamos un CTE envuelto en SQL dinámico (porque nuestra aplicación requiere clasificación dinámica del lado del servidor de datos) dentro de un procedimiento almacenado. Puedo proporcionar un ejemplo básico si lo desea.

No he tenido la oportunidad de ver el T/SQL que produce LINQ. ¿Alguien puede publicar una muestra?

No utilizamos LINQ o acceso directo a las tablas, ya que requerimos la capa adicional de seguridad (siempre que el SQL dinámico lo rompa).

Algo como esto debería hacer el truco. Usted puede agregar en los valores parametrizados para los parámetros, etc.

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: una ** pista para el ejemplo ** que ha proporcionado: con 'sp_executesql' tiene la posibilidad de pasar los parámetros de forma segura, por ejemplo:' EXECUTE sp_executesql 'WITH myCTE AS ... WHERE Col4 = @ p1) ... ',' @ p1 nvarchar (max) ', @ ValueForCol4'. Seguro en este contexto significa que es robusto contra la inyección SQL: puede pasar todos los valores posibles dentro de la variable '@ ValueForCol4' - incluso' '-' ', ¡y la consulta seguirá funcionando! – Matt

+1

@mrdenny Hola, en lugar de la concatenación de la consulta que usar algo como esto: SELECT ROW_NUMBER() OVER (ORDER BY CASO CUANDO @CampoId = 1 ENTONCES CUANDO Id @CampoId = 2, HACER campo2 FIN) ' – Ezequiel

+0

que pueden producir algunos horribles planes de ejecución SQL. – mrdenny

0

puede mejorar aún más el rendimiento, chech este

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 

si va a utilizar el de esta manera se le dará un mejor resultado:

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

razón: porque está utilizando el dónde clase en la tabla CityEntities que eliminará muchos registros antes de unirse a MtCity, así que 100% seguro de que aumentará el rendimiento muchas veces ...

Responde de todos modos por rodrigoelp i s realmente útil.

Gracias

+0

Dudo que haya algún impacto en el rendimiento al usar este consejo. No se puede encontrar una referencia para esto pero el orden de combinación interna en la consulta puede diferir del orden de unión real. Este último se decide mediante el optimizador de consultas utilizando las estadísticas de la tabla y las estimaciones de costos de operación. –

+0

@ImreP: Esto podría en realidad corresponderse con el [método de búsqueda que he descrito] (http://stackoverflow.com/a/19609720/521799). Aunque no estoy seguro de dónde '@ p0' y más específicamente' @ p1' provienen de –

47

Intente utilizar

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

para obtener las filas de 501 a 600 en el servidor SQL, sin tener que cargar en la memoria. Tenga en cuenta que esta sintaxis se ha convertido en disponible con SQL Server 2012 única

+0

, creo que esto es incorrecto. El SQL que se muestra muestra las filas del 502-601 (a menos que tenga cero indexación). – Smudge202

10

Mientras LINQ a SQL generará una cláusula OFFSET (posiblemente emulado usando ROW_NUMBER() OVER()as others have mentioned), hay una manera completamente diferente, mucho más rápido para realizar la paginación en SQL. Esto a menudo se llama el "método de búsqueda" como se describe en 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 

Los @previousScore y @previousPlayerId valores son los valores respectivos del último registro de la página anterior. Esto le permite buscar la página "siguiente". Si la dirección ORDER BY es ASC, simplemente use >.

Con el método anterior, no puede pasar de inmediato a la página 4 sin haber obtenido primero los 40 registros anteriores. Pero a menudo, no quieres ir tan lejos de todos modos. En cambio, obtiene una consulta mucho más rápida que podría obtener datos en tiempo constante, según su indexación. Además, sus páginas permanecen "estables", sin importar si los datos subyacentes cambian (por ejemplo, en la página 1, mientras está en la página 4).

Esta es la mejor manera de implementar la paginación cuando la carga es más lenta en las aplicaciones web, por ejemplo.

Nota, el "método de búsqueda" también se llama keyset paging.

1

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

En t0 son todos los registros En T1 son sólo aquellos que corresponden a esa página

0

Puede implementar la paginación de esta manera sencilla mediante el paso 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 
Cuestiones relacionadas