Devrais-je utiliser les méthodes Skip()
et Take()
de LINQ pour la pagination ou implémenter ma propre pagination avec une requête SQL?
Lequel est le plus efficace? Pourquoi devrais-je choisir l'un sur l'autre?
J'utilise SQL Server 2008, ASP.NET MVC et LINQ.
En essayant de répondre brièvement à votre doute, si vous exécutez les méthodes skip(n).take(m)
sur linq (avec SQL 2005/2008 en tant que serveur de base de données), votre requête utilisera l'instruction Select ROW_NUMBER() Over ...
, avec est en quelque sorte la pagination directe dans le moteur SQL.
Pour vous donner un exemple, j'ai une table de base de données appelée mtcity
et j'ai écrit la requête suivante (fonctionne également avec linq to entity):
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 requête résultante sera:
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]
Ce qui est un accès de données fenêtré (assez cool, btw cuz renverra des données depuis le tout début et aura accès à la table tant que les conditions seront remplies). Ce sera très similaire à:
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
À l'exception de ce fait, cette seconde requête sera exécutée plus rapidement que le résultat linq car elle utilisera exclusivement l'index pour créer la fenêtre d'accès aux données. cela signifie que si vous avez besoin d'un filtrage, celui-ci doit figurer (ou doit figurer) dans la liste Entity (où la ligne est créée) et certains index doivent également être créés pour maintenir les bonnes performances.
Maintenant, quoi de mieux?
Si vous avez un flux de travail assez solide dans votre logique, implémenter la méthode SQL appropriée sera compliqué. Dans ce cas, LINQ sera la solution.
Si vous pouvez réduire cette partie de la logique directement en SQL (dans une procédure stockée), ce sera encore mieux car vous pouvez implémenter la seconde requête que je vous ai montrée (à l'aide d'index) et permettre à SQL de générer et de stocker le plan d'exécution du requête (amélioration des performances).
Essayez d'utiliser
FROM [TableX]
ORDER BY [FieldX]
OFFSET 500 ROWS
FETCH NEXT 100 ROWS ONLY
pour obtenir les lignes de 501 à 600 dans le serveur SQL, sans les charger en mémoire. Notez que cette syntaxe est devenue disponible avec SQL Server 2012 uniquement
Alors que LINQ-to-SQL générera une clause OFFSET
(éventuellement émulée à l'aide de ROW_NUMBER() OVER()
comme d'autres l'ont déjà mentionné ), il existe un moyen complètement différent, beaucoup plus rapide effectuer une pagination en SQL. Ceci est souvent appelé la "méthode de recherche" comme décrit dans cet article de blog ici .
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
Le @previousScore
et @previousPlayerId
valeurs sont les valeurs respectives du dernier enregistrement de la page précédente. Cela vous permet d'aller chercher la page "suivante". Si la ORDER BY
direction est ASC
, utilisez simplement >
au lieu.
Avec la méthode ci-dessus, vous ne pouvez pas accéder directement à la page 4 sans avoir au préalable récupéré les 40 enregistrements précédents. Mais souvent, de toute façon, vous ne voulez pas sauter aussi loin. Au lieu de cela, vous obtenez une requête beaucoup plus rapide qui peut éventuellement extraire des données en temps constant, en fonction de votre indexation. De plus, vos pages restent "stables", peu importe si les données sous-jacentes changent (par exemple, à la page 1, pendant que vous êtes à la page 4).
C'est le meilleur moyen d'implémenter la pagination, par exemple lorsque vous chargez plus de données dans des applications Web.
Notez que la "méthode de recherche" est également appelée pagination de jeu de clés .
LinqToSql convertira automatiquement un .Skip (N1) .Take (N2) en syntaxe TSQL pour vous. En fait, chaque "requête" que vous effectuez dans Linq consiste simplement à créer une requête SQL pour vous en arrière-plan. Pour tester cela, exécutez simplement SQL Profiler pendant que votre application est en cours d'exécution.
La méthodologie de saut/prise a très bien fonctionné pour moi et d’autres à partir de ce que je lisais.
Par curiosité, quel type de question de recherche automatique avez-vous, que vous croyez être plus efficace que le saut/reprise de Linq?
Nous utilisons un CTE encapsulé dans SQL dynamique (car notre application nécessite un tri dynamique du côté serveur de données) au sein d'une procédure stockée. Je peux donner un exemple de base si vous le souhaitez.
Je n'ai pas eu l'occasion d'examiner le T/SQL produit par LINQ. Quelqu'un peut-il envoyer un échantillon?
Nous n'utilisons ni LINQ, ni un accès direct aux tables, car nous avons besoin d'une couche de sécurité supplémentaire (à condition que le code SQL dynamique le casse un peu).
Quelque chose comme ça devrait faire l'affaire. Vous pouvez ajouter des valeurs paramétrées pour les paramètres, 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'
Dans 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]
Dans t0 sont tous les enregistrements dans t1 ne sont que ceux correspondant à cette page
vous pouvez encore améliorer les performances, chech cette
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 vous utilisez le de cette manière, vous obtiendrez un meilleur résultat:
From dbo.MtCity t0
Inner Join CityEntities c on c.CodCity = t0.CodCity
raison: parce que vous utilisez la classe where de la table CityEntities qui éliminera de nombreux enregistrements avant de rejoindre MtCity, vous êtes donc sûr à 100% que les performances seront multipliées ...
Quoi qu'il en soit, la réponse de rodrigoelp est vraiment utile.
Merci
En 2008, nous ne pouvons pas utiliser Skip (). Take ()
Le chemin est:
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();
Vous pouvez implémenter la pagination de cette manière simple en passant à 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