¿Debo usar el método LINQ Skip()
y Take()
para la paginación, o implementar mi propia paginación con una consulta SQL?
¿Cuál es más eficiente? ¿Por qué elegiría uno sobre el otro?
Estoy usando SQL Server 2008, ASP.NET MVC y LINQ.
¿Debo usar el método LINQ Skip()
y Take()
para la paginación, o implementar mi propia paginación con una consulta SQL?
¿Cuál es más eficiente? ¿Por qué elegiría uno sobre el otro?
Estoy usando SQL Server 2008, ASP.NET MVC y LINQ.
Respuestas:
Tratando de darle una respuesta breve a su duda, si ejecuta los skip(n).take(m)
métodos en linq (con SQL 2005/2008 como servidor de base de datos) su consulta estará usando la Select ROW_NUMBER() Over ...
declaración, con una paginación directa de alguna manera en el motor SQL.
Dándote un ejemplo, tengo una tabla db llamada mtcity
y escribí la siguiente consulta (también funciona con linq para 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 a datos en ventana (muy bien, por cierto, porque 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 ejecutará más rápido que el resultado de linq porque usará exclusivamente el índice para crear la ventana de acceso a datos; esto significa que, si necesita algún filtrado, el filtrado debe estar (o debe estar) en la lista de Entidades (donde se crea la fila) y también deben crearse 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 adecuada de SQL será complicado. En ese caso, LINQ será la solución.
Si puede reducir 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 del consulta (mejora del rendimiento).
Intenta usar
FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY
para obtener las filas 501 a 600 en el servidor SQL, sin cargarlas en la memoria. Tenga en cuenta que esta sintaxis se ha convertido en disponible con SQL Server 2012 sólo se
Si bien LINQ-to-SQL generará una OFFSET
cláusula (posiblemente emulada usando ROW_NUMBER() OVER()
como han mencionado otros ), existe una forma completamente diferente y mucho más rápida de realizar la paginación en SQL. Esto a menudo se denomina "método de búsqueda", como se describe en esta publicación de blog aquí .
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 valores @previousScore
y @previousPlayerId
son los valores respectivos del último registro de la página anterior. Esto le permite buscar la página "siguiente". Si la ORDER BY
dirección es ASC
, simplemente use >
en su lugar.
Con el método anterior, no puede saltar inmediatamente a la página 4 sin haber obtenido primero los 40 registros anteriores. Pero a menudo, no querrás saltar 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 se carga de forma diferida más datos en aplicaciones web, por ejemplo.
Tenga en cuenta que el "método de búsqueda" también se denomina paginación de conjuntos de claves .
LinqToSql convertirá automáticamente un .Skip (N1) .Take (N2) en la sintaxis TSQL por usted. De hecho, cada "consulta" que hace en Linq, en realidad es simplemente crear una consulta SQL para usted en segundo plano. Para probar esto, simplemente ejecute SQL Profiler mientras se ejecuta su aplicación.
La metodología de saltar / tomar ha funcionado muy bien para mí y para otros por lo que leí.
Por curiosidad, ¿qué tipo de consulta de autopaginación tiene, que cree que es más eficiente que la omisión / toma de Linq?
Usamos un CTE envuelto en SQL dinámico (porque nuestra aplicación requiere una 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 usamos LINQ o acceso directo a las tablas ya que requerimos la capa adicional de seguridad (dado que el SQL dinámico rompe esto de alguna manera).
Algo como esto debería funcionar. Puede agregar valores parametrizados para 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'
sp_executesql
usted tiene la posibilidad de pasar parámetros de una manera 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 de SQL; puede pasar todos los valores posibles dentro de la variable @ValueForCol4
, incluso '--'
, ¡y la consulta seguirá funcionando!
SELECT ROW_NUMBER() OVER (ORDER BY CASE WHEN @CampoId = 1 THEN Id WHEN @CampoId = 2 THEN field2 END)
ROW_NUMBER() OVER()
emulación de desplazamiento. Ver también: 4guysfromrolla.com/webtech/042606-1.shtml
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 están todos los registros En t1 son solo los correspondientes a esa página
El enfoque que estoy dando es la paginación más rápida que puede lograr el servidor SQL. He probado esto en 5 millones de registros. Este enfoque es mucho mejor que "DESPLAZAR 10 FILAS, BUSCAR SIGUIENTES 10 FILAS SOLAMENTE" proporcionado por SQL Server.
-- The below given code computes the page numbers and the max row of previous page
-- Replace <<>> with the correct table data.
-- Eg. <<IdentityColumn of Table>> can be EmployeeId and <<Table>> will be dbo.Employees
DECLARE @PageNumber int=1; --1st/2nd/nth page. In stored proc take this as input param.
DECLARE @NoOfRecordsPerPage int=1000;
DECLARE @PageDetails TABLE
(
<<IdentityColumn of Table>> int,
rownum int,
[PageNumber] int
)
INSERT INTO @PageDetails values(0, 0, 0)
;WITH CTE AS
(
SELECT <<IdentityColumn of Table>>, ROW_NUMBER() OVER(ORDER BY <<IdentityColumn of Table>>) rownum FROM <<Table>>
)
Insert into @PageDetails
SELECT <<IdentityColumn of Table>>, CTE.rownum, ROW_NUMBER() OVER (ORDER BY rownum) as [PageNumber] FROM CTE WHERE CTE.rownum%@NoOfRecordsPerPage=0
--SELECT * FROM @PageDetails
-- Actual pagination
SELECT TOP (@NoOfRecordsPerPage)
FROM <<Table>> AS <<Table>>
WHERE <<IdentityColumn of Table>> > (SELECT <<IdentityColumn of Table>> FROM
@PageDetails WHERE PageNumber=@PageNumber)
ORDER BY <<Identity Column of Table>>
puede mejorar aún más el rendimiento, compruebe esto
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 usa el from de esta manera, dará un mejor resultado:
From dbo.MtCity t0
Inner Join CityEntities c on c.CodCity = t0.CodCity
motivo: porque está utilizando la clase where en la tabla CityEntities que eliminará muchos registros antes de unirse a MtCity, así que 100% seguro de que aumentará el rendimiento muchas veces ...
De todos modos, la respuesta de rodrigoelp es realmente útil.
Gracias
@p0
y más específicamente de dónde @p1
vengo
Puede implementar la paginación de esta manera simple pasando 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
En 2008 no podemos usar Skip (). Take ()
El camino es:
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();