Paginación
La paginación significa recuperar resultados en páginas, en lugar de todos a la vez; esta operación se suele realizar en conjuntos de resultados grandes, donde se muestra una interfaz de usuario que permite al usuario ir a la página siguiente o anterior de los resultados.
Advertencia
Sin importar el método de paginación usado, asegúrese siempre de que la ordenación sea totalmente única. Por ejemplo, si los resultados se ordenan solo por fecha, pero puede haber varios resultados con la misma fecha, los resultados se podrían omitir al paginar, ya que se ordenan de forma diferente entre dos consultas paginadas. La ordenación por fecha e id., o cualquier otra propiedad única o combinación de propiedades, hace que la ordenación sea totalmente única y evita este problema. Tenga en cuenta que las bases de datos relacionales no aplican ninguna ordenación de forma predeterminada, incluso en la clave principal.
Nota:
Azure Cosmos DB cuenta con su propio mecanismo para la paginación, consulte la página de documentación dedicada.
Paginación de desplazamiento
Una manera común de implementar la paginación con bases de datos es usar los operadores LINQ Skip
y Take
(OFFSET
y LIMIT
en SQL). Dado un tamaño de página de 10 resultados, la tercera página se puede capturar con EF Core de la siguiente manera:
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
Desafortunadamente, aunque esta técnica es muy intuitiva, también tiene algunas deficiencias graves:
- La base de datos todavía debe procesar las primeras 20 entradas, incluso si no se devuelven a la aplicación; esto crea una carga de cálculo posiblemente significativa que aumenta con el número de filas que se omiten.
- Si se producen actualizaciones a la vez, la paginación puede acabar omitiendo determinadas entradas o mostrándolas dos veces. Por ejemplo, si se quita una entrada a medida que el usuario se mueve de la página 2 a la 3, el conjunto de resultados completo "cambia hacia arriba" y se omitiría una entrada.
Paginación de conjunto de claves
La alternativa recomendada a la paginación de desplazamiento, a veces denominada paginación de conjunto de claves o paginación basada en búsqueda, es simplemente usar una cláusula WHERE
para omitir filas en lugar de un desplazamiento. Esto significa recordar los valores pertinentes de la última entrada capturada (en lugar de su desplazamiento) y pedir las siguientes filas después de esa fila. Por ejemplo, suponiendo que la última entrada de la última página capturada tenía un valor de identificador de 55, simplemente haríamos lo siguiente:
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
Suponiendo que se define un índice en PostId
, esta consulta es muy eficaz y tampoco es sensible a los cambios simultáneos que se producen en valores de identificador inferiores.
La paginación del conjunto de claves es adecuada para las interfaces de paginación en las que el usuario navega hacia delante y hacia atrás, pero no admite el acceso aleatorio, donde el usuario puede saltar a cualquier página específica. La paginación de acceso aleatorio requiere el uso de la paginación de desplazamiento, como se explicó anteriormente; debido a las deficiencias de paginación de desplazamiento, considere cuidadosamente si realmente se requiere la paginación de acceso aleatorio para su caso de uso, o si la navegación de página siguiente o anterior es suficiente. Si es necesaria la paginación de acceso aleatorio, una implementación sólida podría usar la paginación del conjunto de claves al navegar a la página siguiente o anterior y desplazar la navegación al saltar a cualquier otra página.
Varias claves de paginación
Al usar la paginación del conjunto de claves, con frecuencia es necesario ordenar por más de una propiedad. Por ejemplo, la siguiente consulta pagina por fecha e identificador:
var lastDate = new DateTime(2020, 1, 1);
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.Date)
.ThenBy(b => b.PostId)
.Where(b => b.Date > lastDate || (b.Date == lastDate && b.PostId > lastId))
.Take(10)
.ToList();
Esto garantiza que la página siguiente elija exactamente dónde finalizó la anterior. A medida que se agregan más claves de ordenación, se pueden agregar cláusulas adicionales.
Nota:
La mayoría de las bases de datos SQL admiten una versión más sencilla y eficaz de lo anterior, mediante valores de fila: WHERE (Date, Id) > (@lastDate, @lastId)
. EF Core no admite actualmente la expresión de esto en consultas LINQ; se rastrea mediante #26822.
Índices
Al igual que con cualquier otra consulta, la indexación adecuada es fundamental para un buen rendimiento: asegúrese de tener índices en su lugar que se correspondan con el orden de paginación. Si se ordena por más de una columna, se puede definir un índice sobre esas varias columnas; esto se denomina índice compuesto.
Para obtener más información, consulte la página de documentación de los índices.
Recursos adicionales
- Para obtener más información sobre las deficiencias de la paginación basada en desplazamiento y sobre la paginación del conjunto de claves, consulte esta entrada.
- Sesión de standup de la comunidad de datos de .NET donde se describe la paginación y la demostración de todos los conceptos anteriores.
- Presentación técnica en profundidad que compara la paginación de desplazamiento y la de conjunto de claves. Aunque el contenido se ocupa de la base de datos PostgreSQL, la información general también es válida para otras bases de datos relacionales.
- Para obtener extensiones sobre EF Core que simplifican la paginación del conjunto de claves, consulte MR.EntityFrameworkCore.KeysetPagination y MR.AspNetCore.Pagination.