Pagination NHibernate avec SQL Server
-
22-07-2019 - |
Question
Lors de l'utilisation de SetFirstResult (start)
et de méthodes SetMaxResults (count)
pour implémenter la pagination, j'ai remarqué que la requête générée ne fait qu'un select top count * de some_table
et le paramètre start
n'est pas pris en compte, ou du moins pas au niveau de la base de données. Il semble que si je demande à NHibernate d’exécuter la requête suivante:
var users = session.CreateCriteria<User>()
.SetFirstResult(100)
.SetMaxResults(5)
.List<User>();
105 enregistrements transitent entre le serveur de base de données et l’application, qui se chargera de supprimer les 100 premiers enregistrements. Avec les tables contenant plusieurs lignes, cela pourrait poser problème.
J'ai vérifié qu'avec une base de données SQLite , NHibernate tire parti du OFFSET et
LIMIT
pour filtrer les résultats au niveau de la base de données. Je suis conscient du fait qu'il n'existe pas d'équivalent entre le mot clé OFFSET
et le ROWNUM
d'Oracle dans SQL Server 2000, mais existe-t-il une solution de contournement? Qu'en est-il de SQL Server 2005/2008?
La solution
T-SQL, la variante du langage SQL utilisée par Microsoft SQL Server, ne comporte pas de clause limit
. Il possède un modificateur select top {...}
dont NHibernate tire parti avec SQL Server 2000.
Avec SQL Server 2005, Microsoft a introduit la fonction Row_Number () over (order by {...})
, qui peut être utilisée en remplacement de la clause limit
. et vous pouvez voir NHibernate en tirer parti avec SQL Server 2005/2008.
Une requête pour SQLite pourrait ressembler à
select c.[ID], c.[Name]
from [Codes] c
where c.[Key] = 'abcdef'
order by c.[Order]
limit 20 offset 40
alors qu'une requête similaire pour SQL Server 2005 pourrait ressembler à
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]
ou, à l'aide d'expressions de table communes, cela pourrait ressembler à
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]
Il existe également un moyen de le faire dans 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]
Autres conseils
Nhibernate est suffisamment intelligent pour optimiser les requêtes. Si vous sélectionnez les 10 premières lignes, il utilisera l'instruction TOP
. Si vous ne sélectionnez pas les premières lignes, il utilisera RowNum
.
Dans SQL 2000, il n’existe pas de fonction RowNum
. C’est pourquoi il est impossible avec une requête classique de sélectionner le nombre de lignes requis. Pour SQL 2000, comme je le sais pour une telle optimisation, des vues ont été utilisées.
Dans SQL 2005/2008, la requête sélectionne uniquement les lignes requises.