Paginación de NHibernate con SQL Server
-
22-07-2019 - |
Pregunta
Cuando se usan los métodos SetFirstResult (inicio)
y SetMaxResults (count)
para implementar la paginación, he notado que la consulta generada solo hace un select top count * de some_table
y no tiene en cuenta el parámetro start
o al menos no a nivel de la base de datos. Parece que si le digo a NHibernate que ejecute la siguiente consulta:
var users = session.CreateCriteria<User>()
.SetFirstResult(100)
.SetMaxResults(5)
.List<User>();
105 registros transitarán entre el servidor de la base de datos y la aplicación que se encargará de eliminar los primeros 100 registros. Con tablas que contienen muchas filas esto podría ser un problema.
He verificado que con una base de datos SQLite , NHibernate aprovecha el código OFFSET > y
LIMIT
palabras clave para filtrar resultados a nivel de base de datos. Soy consciente de que no hay un equivalente de la palabra clave OFFSET
y ROWNUM
de Oracle en SQL Server 2000, pero ¿hay alguna solución? ¿Qué hay de SQL Server 2005/2008?
Solución
T-SQL, la variante del lenguaje SQL que utiliza Microsoft SQL Server, no tiene una cláusula limit
. Tiene un modificador select top {...}
que puede ver que NHibernate aprovecha con SQL Server 2000.
Con SQL Server 2005, Microsoft introdujo la función Row_Number () over (ordenar por {...})
que se puede usar como reemplazo de la cláusula limit
, y puede ver que NHibernate se aprovecha de eso con SQL Server 2005/2008.
Una consulta para SQLite podría verse
select c.[ID], c.[Name]
from [Codes] c
where c.[Key] = 'abcdef'
order by c.[Order]
limit 20 offset 40
mientras que una consulta similar para SQL Server 2005 podría verse
select c.[ID], c.[Name]
from (
select c.[ID], c.[Name], c.[Order]
, [!RowNum] = Row_Number() over (order by c.[Order])
from [Codes] c
where c.[Key] = 'abcdef'
) c
where c.[!RowNum] > 40 and c.[!RowNum] <= 60
order by c.[Order]
o, usando Expresiones de tabla comunes, podría parecer
with
[Source] as (
select c.[ID], c.[Name], c.[Order]
, [!RowNum] = Row_Number() over (order by c.[Order])
from [Codes] c
where c.[Key] = 'abcdef'
)
select c.[ID], c.[Name]
from [Source] c
where c.[!RowNum] > 40 and c.[!RowNum] <= 60
order by c.[Order]
También hay una manera de hacerlo en SQL Server 2000
select c.[ID], c.[Name]
from (
select top 20 c.[ID], c.[Name], c.[Order]
from (
select top 60 c.[ID], c.[Name], c.[Order]
from [Codes] c
where c.[Key] = 'abcdef'
order by c.[Order]
) c
order by c.[Order] desc
) c
order by c.[Order]
Otros consejos
Nhibernate es lo suficientemente inteligente como para optimizar la consulta. Si selecciona las primeras 10 filas, usará la instrucción TOP
. Si selecciona no las primeras filas, usará RowNum
.
En sql 2000 no existe la función RowNum
, es por eso que con la consulta habitual es imposible seleccionar el número requerido de filas. Para sql 2000, como sé, se utilizaron vistas de optimización.
En sql 2005/2008 la consulta seleccionará solo las filas requeridas.