Pagination efficace dans de grandes quantités de données (C#)
par Scott Mitchell
L’option de pagination par défaut d’un contrôle de présentation de données n’est pas adaptée lors de l’utilisation de grandes quantités de données, car son contrôle de source de données sous-jacent récupère tous les enregistrements, même si seul un sous-ensemble de données est affiché. Dans de telles circonstances, nous devons nous tourner vers la pagination personnalisée.
Introduction
Comme nous l’avons vu dans le tutoriel précédent, la pagination peut être implémentée de deux façons :
- La pagination par défaut peut être implémentée en vérifiant simplement l’option Activer la pagination dans la balise active du contrôle Web de données. Toutefois, chaque fois que vous affichez une page de données, ObjectDataSource récupère tous les enregistrements, même si seul un sous-ensemble d’entre eux est affiché dans la page
- La pagination personnalisée améliore les performances de la pagination par défaut en récupérant uniquement les enregistrements de la base de données qui doivent être affichés pour la page particulière des données demandées par l’utilisateur ; toutefois, la pagination personnalisée implique un peu plus d’efforts pour implémenter que la pagination par défaut
En raison de la facilité d’implémentation, cochez simplement une case et vous avez terminé ! la pagination par défaut est une option attrayante. Son approche naïve de la récupération de tous les enregistrements, cependant, en fait un choix invraisemblable lors de la pagination de suffisamment grandes quantités de données ou pour les sites avec de nombreux utilisateurs simultanés. Dans de telles circonstances, nous devons nous tourner vers la pagination personnalisée afin de fournir un système réactif.
Le défi de la pagination personnalisée est de pouvoir écrire une requête qui retourne l’ensemble précis d’enregistrements nécessaires pour une page de données particulière. Heureusement, Microsoft SQL Server 2005 fournit un nouveau mot clé pour les résultats de classement, ce qui nous permet d’écrire une requête qui peut récupérer efficacement le sous-ensemble approprié d’enregistrements. Dans ce tutoriel, nous allons voir comment utiliser ce nouveau mot clé SQL Server 2005 pour implémenter la pagination personnalisée dans un contrôle GridView. Bien que l’interface utilisateur pour la pagination personnalisée soit identique à celle de la pagination par défaut, l’exécution pas à pas d’une page à l’autre à l’aide de la pagination personnalisée peut être plusieurs ordres de grandeur plus rapides que la pagination par défaut.
Remarque
Le gain de performances exact exposé par la pagination personnalisée dépend du nombre total d’enregistrements paginés et de la charge placée sur le serveur de base de données. À la fin de ce tutoriel, nous allons examiner certaines métriques approximatives qui présentent les avantages des performances obtenues via la pagination personnalisée.
Étape 1 : Présentation du processus de pagination personnalisé
Lors de la pagination des données, les enregistrements précis affichés dans une page dépendent de la page des données demandées et du nombre d’enregistrements affichés par page. Par exemple, imaginez que nous voulions parcourir les 81 produits, affichant 10 produits par page. Lors de l’affichage de la première page, nous voulons des produits 1 à 10 ; lors de l’affichage de la deuxième page, nous seriez intéressés par les produits 11 à 20, et ainsi de suite.
Il existe trois variables qui déterminent quels enregistrements doivent être récupérés et comment l’interface de pagination doit être rendue :
- Démarrez l’index de ligne de la première ligne de la page de données à afficher ; cet index peut être calculé en multipliant l’index de page par les enregistrements à afficher par page et en en ajoutant un. Par exemple, lors de la pagination des enregistrements 10 à la fois, pour la première page (dont l’index de page est 0), l’index de ligne de démarrage est 0 * 10 + 1 ou 1 ; pour la deuxième page (dont l’index de page est 1), l’index de ligne de début est 1 * 10 + 1 ou 11.
- Nombre maximal d’enregistrements à afficher par page. Cette variable est appelée lignes maximales, car pour la dernière page, il peut y avoir moins d’enregistrements retournés que la taille de la page. Par exemple, lors de la pagination des 81 produits 10 enregistrements par page, la neuvième et dernière page n’aura qu’un seul enregistrement. Aucune page n’affiche toutefois plus d’enregistrements que la valeur Nombre maximal de lignes.
- Nombre total d’enregistrements le nombre total d’enregistrements paginés. Bien que cette variable ne soit pas nécessaire pour déterminer les enregistrements à récupérer pour une page donnée, elle détermine l’interface de pagination. Par exemple, si 81 produits sont paginés, l’interface de pagination sait afficher neuf numéros de page dans l’interface utilisateur de pagination.
Avec la pagination par défaut, l’index de ligne de démarrage est calculé en tant que produit de l’index de page et de la taille de page plus un, tandis que les lignes maximales sont simplement la taille de page. Étant donné que la pagination par défaut récupère tous les enregistrements de la base de données lors du rendu d’une page de données, l’index de chaque ligne est connu, ce qui rend le déplacement vers la ligne Démarrer l’index de ligne une tâche triviale. De plus, le nombre total d’enregistrements est facilement disponible, car il s’agit simplement du nombre d’enregistrements dans dataTable (ou de l’objet utilisé pour contenir les résultats de la base de données).
Étant donné les variables d’index de ligne de début et de ligne maximale, une implémentation de pagination personnalisée doit uniquement retourner le sous-ensemble précis d’enregistrements commençant à l’index de ligne de début et jusqu’au nombre maximal d’enregistrements après cela. La pagination personnalisée offre deux défis :
- Nous devons pouvoir associer efficacement un index de ligne à chaque ligne de l’ensemble des données paginées afin que nous puissions commencer à retourner des enregistrements à l’index de ligne de début spécifié.
- Nous devons fournir le nombre total d’enregistrements paginés via
Dans les deux étapes suivantes, nous allons examiner le script SQL nécessaire pour répondre à ces deux défis. En plus du script SQL, nous devons également implémenter des méthodes dans dal et BLL.
Étape 2 : renvoi du nombre total d’enregistrements paginés
Avant d’examiner comment récupérer le sous-ensemble précis d’enregistrements pour la page affichée, examinons d’abord comment retourner le nombre total d’enregistrements paginés. Ces informations sont nécessaires pour configurer correctement l’interface utilisateur de pagination. Le nombre total d’enregistrements retournés par une requête SQL particulière peut être obtenu à l’aide de la fonction d’agrégation.COUNT
Par exemple, pour déterminer le nombre total d’enregistrements dans la Products
table, nous pouvons utiliser la requête suivante :
SELECT COUNT(*)
FROM Products
Ajoutons une méthode à notre dal qui retourne ces informations. En particulier, nous allons créer une méthode DAL appelée TotalNumberOfProducts()
qui exécute l’instruction SELECT
indiquée ci-dessus.
Commencez par ouvrir le Northwind.xsd
fichier Typed DataSet dans le App_Code/DAL
dossier. Ensuite, cliquez avec le bouton droit sur le ProductsTableAdapter
concepteur et choisissez Ajouter une requête. Comme nous l’avons vu dans les didacticiels précédents, cela nous permettra d’ajouter une nouvelle méthode à la dal qui, lorsqu’elle est appelée, exécute une instruction SQL ou une procédure stockée particulière. Comme avec nos méthodes TableAdapter dans les didacticiels précédents, pour celui-ci, optez pour utiliser une instruction SQL ad hoc.
Figure 1 : Utiliser une instruction SQL ad hoc
Dans l’écran suivant, nous pouvons spécifier le type de requête à créer. Étant donné que cette requête retourne une valeur scalaire unique, le nombre total d’enregistrements dans la Products
table choisit l’option SELECT
qui retourne une option de valeur singe.
Figure 2 : Configurer la requête pour utiliser une instruction SELECT qui retourne une valeur unique
Après avoir indiqué le type de requête à utiliser, nous devons ensuite spécifier la requête.
Figure 3 : Utiliser la requête SELECT COUNT(*) FROM Products
Enfin, spécifiez le nom de la méthode. Comme mentionné ci-dessus, nous allons utiliser TotalNumberOfProducts
.
Figure 4 : Nommer la méthode DAL TotalNumberOfProducts
Après avoir cliqué sur Terminer, l’Assistant ajoute la TotalNumberOfProducts
méthode au dal. Les méthodes de retour scalaires dans les types nullables de retour DAL, au cas où le résultat de la requête SQL était NULL
. Toutefois, notre COUNT
requête retourne toujours une valeur non-valeurNULL
; quelle que soit la méthode DAL, elle renvoie un entier nullable.
En plus de la méthode DAL, nous avons également besoin d’une méthode dans la BLL. Ouvrez le ProductsBLL
fichier de classe et ajoutez une TotalNumberOfProducts
méthode qui appelle simplement la méthode dal s TotalNumberOfProducts
:
public int TotalNumberOfProducts()
{
return Adapter.TotalNumberOfProducts().GetValueOrDefault();
}
La méthode DAL TotalNumberOfProducts
renvoie un entier nullable . Toutefois, nous avons créé la ProductsBLL
méthode de classe pour TotalNumberOfProducts
qu’elle retourne un entier standard. Par conséquent, nous devons que la ProductsBLL
méthode de TotalNumberOfProducts
classe retourne la partie valeur de l’entier nullable retourné par la méthode DAL.TotalNumberOfProducts
L’appel à renvoyer GetValueOrDefault()
la valeur de l’entier nullable, s’il existe ; si l’entier nullable est null
, toutefois, il retourne la valeur entière par défaut, 0.
Étape 3 : Retour du sous-ensemble précis d’enregistrements
Notre prochaine tâche consiste à créer des méthodes dans le dal et BLL qui acceptent les variables d’index de ligne de début et de ligne maximale décrites précédemment et retournent les enregistrements appropriés. Avant cela, examinons d’abord le script SQL nécessaire. Le défi auquel nous sommes confrontés est que nous devons être en mesure d’affecter efficacement un index à chaque ligne dans l’ensemble des résultats mis en page afin que nous puissions retourner uniquement ces enregistrements à partir de l’index de ligne de démarrage (et jusqu’au nombre maximal d’enregistrements).
Ce n’est pas un défi s’il existe déjà une colonne dans la table de base de données qui sert d’index de ligne. À première vue, nous pourrions penser que le Products
champ de ProductID
table suffirait, car le premier produit a ProductID
1, le deuxième a 2, et ainsi de suite. Toutefois, la suppression d’un produit laisse un écart dans la séquence, ce qui annule cette approche.
Il existe deux techniques générales utilisées pour associer efficacement un index de ligne aux données à la page, ce qui permet de récupérer le sous-ensemble précis d’enregistrements :
À l’aide du
ROW_NUMBER()
mot clé SQL Server 2005 nouveau dans SQL Server 2005, leROW_NUMBER()
mot clé associe un classement à chaque enregistrement retourné en fonction de certains classements. Ce classement peut être utilisé comme index de ligne pour chaque ligne.L’utilisation d’une variable de table et
SET ROWCOUNT
de l’instruction SQL Server peut être utilisée pour spécifier le nombre total d’enregistrements qu’une requête doit traiter avant deSET ROWCOUNT
terminer ; les variables de table sont des variables T-SQL locales qui peuvent contenir des données tabulaires, comme des tables temporaires. Cette approche fonctionne également bien avec Microsoft SQL Server 2005 et SQL Server 2000 (alors que l’approcheROW_NUMBER()
fonctionne uniquement avec SQL Server 2005).L’idée ici est de créer une variable de table qui a une
IDENTITY
colonne et des colonnes pour les clés primaires de la table dont les données sont paginées. Ensuite, le contenu de la table dont les données sont paginées est vidé dans la variable de table, associant ainsi un index de ligne séquentiel (via laIDENTITY
colonne) pour chaque enregistrement de la table. Une fois la variable de table remplie, uneSELECT
instruction sur la variable de table, jointe à la table sous-jacente, peut être exécutée pour extraire les enregistrements particuliers. L’instructionSET ROWCOUNT
est utilisée pour limiter intelligemment le nombre d’enregistrements qui doivent être vidés dans la variable de table.L’efficacité de cette approche est basée sur le numéro de page demandé, car la
SET ROWCOUNT
valeur est affectée à la valeur de l’index de ligne de début plus les lignes maximales. Lorsque vous paginez des pages à faible nombre, telles que les premières pages de données, cette approche est très efficace. Toutefois, il présente les performances de pagination par défaut lors de la récupération d’une page près de la fin.
Ce didacticiel implémente la pagination personnalisée à l’aide du ROW_NUMBER()
mot clé. Pour plus d’informations sur l’utilisation de la variable de table et SET ROWCOUNT
de la technique, consultez Pagination efficace via de grandes quantités de données.
Le ROW_NUMBER()
mot clé associé à un classement avec chaque enregistrement retourné sur un classement particulier à l’aide de la syntaxe suivante :
SELECT columnList,
ROW_NUMBER() OVER(orderByClause)
FROM TableName
ROW_NUMBER()
retourne une valeur numérique qui spécifie le classement de chaque enregistrement en ce qui concerne l’ordre indiqué. Par exemple, pour voir le classement de chaque produit, classé du plus cher au moins, nous pourrions utiliser la requête suivante :
SELECT ProductName, UnitPrice,
ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products
La figure 5 montre les résultats de cette requête lors de l’exécution dans la fenêtre de requête dans Visual Studio. Notez que les produits sont commandés par prix, ainsi qu’un classement des prix pour chaque ligne.
Figure 5 : Le classement des prix est inclus pour chaque enregistrement retourné
Remarque
ROW_NUMBER()
n’est qu’une des nombreuses nouvelles fonctions de classement disponibles dans SQL Server 2005. Pour une discussion plus approfondie sur , ainsi que les autres fonctions de ROW_NUMBER()
classement, lisez Retour des résultats classés avec Microsoft SQL Server 2005.
Lors du classement des résultats par la colonne spécifiée ORDER BY
dans la OVER
clause (UnitPrice
dans l’exemple ci-dessus), SQL Server doit trier les résultats. Il s’agit d’une opération rapide s’il existe un index cluster sur les colonnes par laquelle les résultats sont classés, ou s’il existe un index de couverture, mais peut être plus coûteux sinon. Pour améliorer les performances des requêtes suffisamment volumineuses, envisagez d’ajouter un index non cluster pour la colonne par laquelle les résultats sont classés. Consultez fonctions de classement et performances dans SQL Server 2005 pour obtenir un aperçu plus détaillé des considérations relatives aux performances.
Les informations de classement retournées par ROW_NUMBER()
ne peuvent pas être utilisées directement dans la WHERE
clause. Toutefois, une table dérivée peut être utilisée pour retourner le ROW_NUMBER()
résultat, qui peut ensuite apparaître dans la WHERE
clause. Par exemple, la requête suivante utilise une table dérivée pour retourner les colonnes ProductName et UnitPrice, ainsi que le ROW_NUMBER()
résultat, puis utilise une WHERE
clause pour retourner uniquement les produits dont le classement des prix est compris entre 11 et 20 :
SELECT PriceRank, ProductName, UnitPrice
FROM
(SELECT ProductName, UnitPrice,
ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products
) AS ProductsWithRowNumber
WHERE PriceRank BETWEEN 11 AND 20
En étendant ce concept un peu plus loin, nous pouvons utiliser cette approche pour récupérer une page spécifique de données en fonction des valeurs d’index de ligne de démarrage souhaitées et valeurs maximales de lignes :
SELECT PriceRank, ProductName, UnitPrice
FROM
(SELECT ProductName, UnitPrice,
ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products
) AS ProductsWithRowNumber
WHERE PriceRank > <i>StartRowIndex</i> AND
PriceRank <= (<i>StartRowIndex</i> + <i>MaximumRows</i>)
Remarque
Comme nous le verrons plus loin dans ce tutoriel, l’indexé StartRowIndex
fourni par ObjectDataSource commence à zéro, tandis que la ROW_NUMBER()
valeur retournée par SQL Server 2005 est indexée à partir de 1. Par conséquent, la WHERE
clause retourne ces enregistrements PriceRank
qui sont strictement supérieurs StartRowIndex
et inférieurs ou égaux à StartRowIndex
+ MaximumRows
.
Maintenant que nous avons abordé comment ROW_NUMBER()
récupérer une page particulière de données en fonction des valeurs d’index de ligne de démarrage et de nombre maximal de lignes, nous devons maintenant implémenter cette logique en tant que méthodes dans la dal et BLL.
Lors de la création de cette requête, nous devons décider de l’ordre par lequel les résultats seront classés ; trions les produits par leur nom par ordre alphabétique. Cela signifie qu’avec l’implémentation de pagination personnalisée dans ce tutoriel, nous ne pouvons pas créer un rapport paginé personnalisé que vous pouvez également trier. Dans le tutoriel suivant, cependant, nous verrons comment ces fonctionnalités peuvent être fournies.
Dans la section précédente, nous avons créé la méthode DAL en tant qu’instruction SQL ad hoc. Malheureusement, l’analyseur T-SQL dans Visual Studio utilisé par l’Assistant TableAdapter n’aime pas la OVER
syntaxe utilisée par la ROW_NUMBER()
fonction. Par conséquent, nous devons créer cette méthode DAL en tant que procédure stockée. Sélectionnez l’Explorateur de serveurs dans le menu Affichage (ou appuyez sur Ctrl+Alt+S) et développez le NORTHWND.MDF
nœud. Pour ajouter une nouvelle procédure stockée, cliquez avec le bouton droit sur le nœud Procédures stockées et choisissez Ajouter une nouvelle procédure stockée (voir la figure 6).
Figure 6 : Ajouter une nouvelle procédure stockée pour la pagination des produits
Cette procédure stockée doit accepter deux paramètres d’entrée entiers @startRowIndex
et @maximumRows
utiliser la ROW_NUMBER()
fonction ordonnée par le ProductName
champ, en retournant uniquement ces lignes supérieures à celles spécifiées @startRowIndex
et inférieures ou égales à @startRowIndex
+ @maximumRow
s. Entrez le script suivant dans la nouvelle procédure stockée, puis cliquez sur l’icône Enregistrer pour ajouter la procédure stockée à la base de données.
CREATE PROCEDURE dbo.GetProductsPaged
(
@startRowIndex int,
@maximumRows int
)
AS
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
CategoryName, SupplierName
FROM
(
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName
FROM Categories
WHERE Categories.CategoryID = Products.CategoryID) AS CategoryName,
(SELECT CompanyName
FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID) AS SupplierName,
ROW_NUMBER() OVER (ORDER BY ProductName) AS RowRank
FROM Products
) AS ProductsWithRowNumbers
WHERE RowRank > @startRowIndex AND RowRank <= (@startRowIndex + @maximumRows)
Après avoir créé la procédure stockée, prenez un moment pour le tester. Cliquez avec le bouton droit sur le nom de la GetProductsPaged
procédure stockée dans l’Explorateur de serveurs et choisissez l’option Exécuter. Visual Studio vous invite ensuite à entrer les paramètres d’entrée et @startRowIndex
@maximumRow
s (voir la figure 7). Essayez différentes valeurs et examinez les résultats.
@startRowIndex et @maximumRows paramètres » />
Figure 7 : Entrer une valeur pour les paramètres et @maximumRows les @startRowIndex paramètres
Après avoir choisi ces valeurs de paramètres d’entrée, la fenêtre Sortie affiche les résultats. La figure 8 montre les résultats lors de la transmission de 10 pour les paramètres et @maximumRows
les @startRowIndex
paramètres.
Figure 8 : Les enregistrements qui s’affichent dans la deuxième page de données sont retournés (cliquez pour afficher l’image de taille complète)
Avec cette procédure stockée créée, nous sommes prêts à créer la ProductsTableAdapter
méthode. Ouvrez l’ensemble de données typé, cliquez avec le Northwind.xsd
bouton droit dans le ProductsTableAdapter
fichier , puis choisissez l’option Ajouter une requête. Au lieu de créer la requête à l’aide d’une instruction SQL ad hoc, créez-la à l’aide d’une procédure stockée existante.
Figure 9 : Créer la méthode DAL à l’aide d’une procédure stockée existante
Ensuite, nous sommes invités à sélectionner la procédure stockée à appeler. Sélectionnez la GetProductsPaged
procédure stockée dans la liste déroulante.
Figure 10 : Choisir la procédure stockée GetProductsPaged dans la liste déroulante
L’écran suivant vous demande ensuite quel type de données est retourné par la procédure stockée : données tabulaires, valeur unique ou aucune valeur. Étant donné que la GetProductsPaged
procédure stockée peut retourner plusieurs enregistrements, indiquez qu’elle retourne des données tabulaires.
Figure 11 : Indiquer que la procédure stockée retourne des données tabulaires
Enfin, indiquez les noms des méthodes que vous souhaitez créer. Comme avec nos didacticiels précédents, poursuivez et créez des méthodes à l’aide du remplissage d’un DataTable et d’un DataTable. Nommez la première méthode FillPaged
et la seconde GetProductsPaged
.
Figure 12 : Nommer les méthodes FillPaged et GetProductsPaged
En plus de créer une méthode DAL pour retourner une page particulière de produits, nous devons également fournir ces fonctionnalités dans la BLL. Comme la méthode DAL, la méthode GetProductsPaged de BLL doit accepter deux entrées entières pour spécifier l’index de ligne de début et les lignes maximales, et doit retourner uniquement ces enregistrements qui se trouvent dans la plage spécifiée. Créez une telle méthode BLL dans la classe ProductsBLL qui appelle simplement dans la méthode GetProductsPaged de DAL, comme suit :
[System.ComponentModel.DataObjectMethodAttribute(
System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPaged(int startRowIndex, int maximumRows)
{
return Adapter.GetProductsPaged(startRowIndex, maximumRows);
}
Vous pouvez utiliser n’importe quel nom pour les paramètres d’entrée de la méthode BLL, mais, comme nous le verrons bientôt, choisissez d’utiliser startRowIndex
et maximumRows
enregistrez-nous à partir d’un peu de travail lors de la configuration d’un ObjectDataSource pour utiliser cette méthode.
Étape 4 : Configuration de ObjectDataSource pour utiliser la pagination personnalisée
Avec les méthodes BLL et DAL pour accéder à un sous-ensemble particulier d’enregistrements terminés, nous sommes prêts à créer un contrôle GridView qui pages par le biais de ses enregistrements sous-jacents à l’aide de la pagination personnalisée. Commencez par ouvrir la EfficientPaging.aspx
page dans le PagingAndSorting
dossier, ajoutez un GridView à la page et configurez-le pour utiliser un nouveau contrôle ObjectDataSource. Dans nos didacticiels passés, nous avons souvent configuré ObjectDataSource pour utiliser la ProductsBLL
méthode de GetProducts
classe. Cette fois,cependant, nous voulons utiliser la méthode à la GetProductsPaged
place, puisque la GetProducts
méthode retourne tous les produits de la base de données, tandis que GetProductsPaged
retourne uniquement un sous-ensemble particulier d’enregistrements.
Figure 13 : Configurer ObjectDataSource pour utiliser la méthode GetProductsPaged de la classe ProductsBLL
Étant donné que nous créons un GridView en lecture seule, prenez un moment pour définir la liste déroulante de méthode dans les onglets INSERT, UPDATE et DELETE sur (Aucun).
Ensuite, l’Assistant ObjectDataSource nous invite à entrer les sources des valeurs des paramètres de GetProductsPaged
startRowIndex
méthode et maximumRows
d’entrée. Ces paramètres d’entrée sont définis automatiquement par GridView. Il vous suffit donc de laisser la source définie sur None, puis de cliquer sur Terminer.
Figure 14 : Laisser les sources de paramètres d’entrée comme aucune
Une fois l’Assistant ObjectDataSource terminé, GridView contient un Objet BoundField ou CheckBoxField pour chacun des champs de données de produit. N’hésitez pas à personnaliser l’apparence de GridView comme vous le voyez. J’ai choisi d’afficher uniquement les ProductName
champs , , QuantityPerUnit
CategoryName
SupplierName
et UnitPrice
BoundFields. Configurez également GridView pour prendre en charge la pagination en cochant la case Activer la pagination dans sa balise active. Après ces modifications, le balisage déclaratif GridView et ObjectDataSource doit ressembler à ce qui suit :
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True">
<Columns>
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
ReadOnly="True" SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName" HeaderText="Supplier"
SortExpression="SupplierName" />
<asp:BoundField DataField="QuantityPerUnit" HeaderText="Qty/Unit"
SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}"
HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetProductsPaged"
TypeName="ProductsBLL">
<SelectParameters>
<asp:Parameter Name="startRowIndex" Type="Int32" />
<asp:Parameter Name="maximumRows" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
Si vous visitez la page via un navigateur, toutefois, gridView n’est pas là où trouver.
Figure 15 : Le GridView n’est pas affiché
GridView est manquant, car ObjectDataSource utilise actuellement 0 comme valeurs pour les paramètres d’entrée et maximumRows
les GetProductsPaged
startRowIndex
paramètres d’entrée. Par conséquent, la requête SQL résultante ne retourne aucun enregistrement et, par conséquent, gridView n’est pas affiché.
Pour résoudre ce problème, nous devons configurer ObjectDataSource pour utiliser la pagination personnalisée. Pour ce faire, procédez comme suit :
- Définissez la propriété
true
ObjectDataSourceEnablePaging
sur ceci indique à ObjectDataSource qu’il doit passer auxSelectMethod
deux paramètres supplémentaires : un pour spécifier l’index de ligne de début (StartRowIndexParameterName
) et l’autre pour spécifier les lignes maximales (MaximumRowsParameterName
). - Définissez les propriétés et
MaximumRowsParameterName
lesStartRowIndexParameterName
propriétés ObjectDataSource en conséquence,StartRowIndexParameterName
etMaximumRowsParameterName
indiquent les noms des paramètres d’entrée transmis à desSelectMethod
fins de pagination personnalisées. Par défaut, ces noms de paramètres sontstartIndexRow
etmaximumRows
, c’est pourquoi, lors de la création de laGetProductsPaged
méthode dans la BLL, j’ai utilisé ces valeurs pour les paramètres d’entrée. Si vous avez choisi d’utiliser différents noms de paramètres pour la méthode BLL,GetProductsPaged
parmaxRows
startIndex
exemple, vous devez définir les propriétés etMaximumRowsParameterName
les propriétés ObjectDataSourceStartRowIndexParameterName
en conséquence (par exemple, startIndex pourStartRowIndexParameterName
et maxRows pourMaximumRowsParameterName
). - Définissez la propriété ObjectDataSource sur
SelectCountMethod
le nom de la méthode qui renvoie le nombre total d’enregistrements mis en page (TotalNumberOfProducts
) rappelez-vous que laProductsBLL
méthode sTotalNumberOfProducts
de classe retourne le nombre total d’enregistrements paginés à l’aide d’une méthode DAL qui exécute uneSELECT COUNT(*) FROM Products
requête. Ces informations sont requises par ObjectDataSource pour restituer correctement l’interface de pagination. - Supprimez les éléments et
<asp:Parameter>
maximumRows
lesstartRowIndex
éléments du balisage déclaratif de ObjectDataSource lors de la configuration de ObjectDataSource via l’Assistant, Visual Studio a automatiquement ajouté deux<asp:Parameter>
éléments pour les paramètres d’entrée de laGetProductsPaged
méthode. En définissantEnablePaging
true
sur , ces paramètres sont transmis automatiquement ; s’ils apparaissent également dans la syntaxe déclarative, ObjectDataSource tente de passer quatre paramètres à laGetProductsPaged
méthode et deux paramètres à laTotalNumberOfProducts
méthode. Si vous oubliez de supprimer ces<asp:Parameter>
éléments, lors de la visite de la page via un navigateur, vous obtenez un message d’erreur comme : ObjectDataSource « ObjectDataSource1 » n’a pas pu trouver une méthode non générique « TotalNumberOfProducts » qui a des paramètres : startRowIndex, maximumRows.
Après avoir apporté ces modifications, la syntaxe déclarative de ObjectDataSource doit ressembler à ce qui suit :
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
SelectMethod="GetProductsPaged" EnablePaging="True"
SelectCountMethod="TotalNumberOfProducts">
</asp:ObjectDataSource>
Notez que les propriétés et SelectCountMethod
les EnablePaging
propriétés ont été définies et que les <asp:Parameter>
éléments ont été supprimés. La figure 16 montre une capture d’écran du Fenêtre Propriétés une fois ces modifications effectuées.
Figure 16 : Pour utiliser la pagination personnalisée, configurez le contrôle ObjectDataSource
Après avoir apporté ces modifications, visitez cette page via un navigateur. Vous devriez voir 10 produits répertoriés, classés par ordre alphabétique. Prenez un moment pour parcourir les données d’une page à la fois. Bien qu’il n’existe aucune différence visuelle du point de vue de l’utilisateur final entre la pagination par défaut et la pagination personnalisée, la pagination personnalisée plus efficacement via de grandes quantités de données, car elle récupère uniquement ces enregistrements qui doivent être affichés pour une page donnée.
Figure 17 : Les données, triées par nom du produit, sont paginées à l’aide de la pagination personnalisée (cliquez pour afficher l’image de taille complète)
Remarque
Avec la pagination personnalisée, la valeur de nombre de SelectCountMethod
pages retournée par les ObjectDataSource est stockée dans l’état d’affichage de GridView. D’autres variables GridView sont SelectedIndex
EditIndex
DataKeys
PageIndex
stockées dans l’état de contrôle, et ainsi de suite sont conservées, quelle que soit la valeur de la propriété de EnableViewState
GridView. Étant donné que la PageCount
valeur est conservée dans les postbacks à l’aide de l’état d’affichage, lors de l’utilisation d’une interface de pagination qui inclut un lien pour vous amener à la dernière page, il est impératif que l’état d’affichage de GridView soit activé. (Si votre interface de pagination n’inclut pas de lien direct vers la dernière page, vous pouvez désactiver l’état d’affichage.)
Cliquer sur le dernier lien de page entraîne une publication et indique à GridView de mettre à jour sa PageIndex
propriété. Si le dernier lien de page est cliqué, GridView affecte sa PageIndex
propriété à une valeur inférieure à sa PageCount
propriété. Lorsque l’état d’affichage est désactivé, la PageCount
valeur est perdue entre les postbacks et la PageIndex
valeur entière maximale est affectée à la place. Ensuite, GridView tente de déterminer l’index de ligne de départ en multipliant les propriétés et PageCount
les PageSize
propriétés. Ainsi, OverflowException
le produit dépasse la taille entière maximale autorisée.
Implémenter la pagination et le tri personnalisés
Notre implémentation de pagination personnalisée actuelle nécessite que l’ordre par lequel les données sont paginées soit spécifié statiquement lors de la création de la GetProductsPaged
procédure stockée. Toutefois, vous avez peut-être remarqué que la balise active gridView contient une case à cocher Activer le tri en plus de l’option Activer la pagination. Malheureusement, l’ajout de la prise en charge du tri à GridView avec notre implémentation de pagination personnalisée actuelle trie uniquement les enregistrements sur la page de données actuellement consultée. Par exemple, si vous configurez GridView pour prendre également en charge la pagination, puis, lors de l’affichage de la première page de données, triez par nom de produit dans l’ordre décroissant, il inverse l’ordre des produits sur la page 1. Comme le montre la figure 18, les Tigres carnarvon sont les premiers produits lors du tri dans l’ordre alphabétique inverse, ce qui ignore les 71 autres produits qui viennent après les Tigres carnarvon, par ordre alphabétique ; seuls ces enregistrements de la première page sont considérés dans le tri.
Figure 18 : Seules les données affichées sur la page active sont triées (cliquez pour afficher l’image pleine taille)
Le tri s’applique uniquement à la page active des données, car le tri se produit une fois que les données ont été récupérées à partir de la méthode BLL s GetProductsPaged
, et cette méthode retourne uniquement ces enregistrements pour la page spécifique. Pour implémenter correctement le tri, nous devons passer l’expression de tri à la GetProductsPaged
méthode afin que les données puissent être classées correctement avant de retourner la page spécifique des données. Nous allons voir comment procéder dans notre prochain tutoriel.
Implémentation de la pagination et de la suppression personnalisées
Si vous activez la suppression de fonctionnalités dans un GridView dont les données sont paginées à l’aide de techniques de pagination personnalisées, vous constaterez que lors de la suppression du dernier enregistrement de la dernière page, GridView disparaît plutôt que de décrémenter correctement les éléments GridView PageIndex
. Pour reproduire ce bogue, activez la suppression pour le didacticiel uniquement que nous venons de créer. Accédez à la dernière page (page 9), où vous devriez voir un seul produit, car nous paginons jusqu’à 81 produits, 10 produits à la fois. Supprimez ce produit.
Lors de la suppression du dernier produit, GridView doit accéder automatiquement à la huitième page, et de telles fonctionnalités sont exposées avec la pagination par défaut. Toutefois, avec la pagination personnalisée après la suppression de ce dernier produit sur la dernière page, GridView disparaît simplement de l’écran. La raison précise pour laquelle cela se produit est un peu au-delà de l’étendue de ce didacticiel ; consultez La suppression du dernier enregistrement sur la dernière page à partir d’un GridView avec pagination personnalisée pour obtenir les détails de bas niveau en fonction de la source de ce problème. En résumé, il s’agit de la séquence suivante d’étapes effectuées par GridView lorsque le bouton Supprimer est cliqué :
- Supprimer l’enregistrement
- Obtenir les enregistrements appropriés à afficher pour l’objet
PageIndex
spécifié etPageSize
- Vérifiez que le
PageIndex
nombre de pages de données dans la source de données n’est pas dépassé ; si c’est le cas, décrémentez automatiquement la propriété GridViewPageIndex
- Lier la page de données appropriée à GridView à l’aide des enregistrements obtenus à l’étape 2
Le problème découle du fait que, à l’étape 2, l’utilisation PageIndex
des enregistrements à afficher est toujours la PageIndex
dernière page dont le seul enregistrement a été supprimé. Par conséquent, à l’étape 2, aucun enregistrement n’est retourné depuis cette dernière page de données ne contient plus d’enregistrements. Ensuite, à l’étape 3, GridView réalise que sa PageIndex
propriété est supérieure au nombre total de pages de la source de données (depuis que nous avons supprimé le dernier enregistrement de la dernière page) et décrémente donc sa PageIndex
propriété. À l’étape 4, GridView tente de se lier aux données récupérées à l’étape 2 ; Toutefois, à l’étape 2, aucun enregistrement n’a été retourné, ce qui entraîne une grille GridView vide. Avec la pagination par défaut, ce problème ne s’affiche pas, car à l’étape 2 , tous les enregistrements sont récupérés à partir de la source de données.
Pour résoudre ce problème, nous avons deux options. La première consiste à créer un gestionnaire d’événements pour le gestionnaire d’événements RowDeleted
GridView qui détermine le nombre d’enregistrements affichés dans la page qui vient d’être supprimé. S’il n’y avait qu’un seul enregistrement, l’enregistrement juste supprimé doit avoir été le dernier et nous devons décrémenter les éléments GridView s PageIndex
. Bien sûr, nous voulons uniquement mettre à jour si PageIndex
l’opération de suppression a réussi, ce qui peut être déterminé en s’assurant que la e.Exception
propriété est null
.
Cette approche fonctionne, car elle met à jour l’étape PageIndex
1 après, mais avant l’étape 2. Par conséquent, à l’étape 2, l’ensemble approprié d’enregistrements est retourné. Pour ce faire, utilisez du code comme suit :
protected void GridView1_RowDeleted(object sender, GridViewDeletedEventArgs e)
{
// If we just deleted the last row in the GridView, decrement the PageIndex
if (e.Exception == null && GridView1.Rows.Count == 1)
// we just deleted the last row
GridView1.PageIndex = Math.Max(0, GridView1.PageIndex - 1);
}
Une autre solution de contournement consiste à créer un gestionnaire d’événements pour l’événement ObjectDataSource RowDeleted
et à définir la AffectedRows
propriété sur la valeur 1. Après avoir supprimé l’enregistrement à l’étape 1 (mais avant de récupérer à nouveau les données à l’étape 2), GridView met à jour sa PageIndex
propriété si une ou plusieurs lignes ont été affectées par l’opération. Toutefois, la AffectedRows
propriété n’est pas définie par ObjectDataSource. Par conséquent, cette étape est omise. Une façon d’exécuter cette étape consiste à définir manuellement la AffectedRows
propriété si l’opération de suppression se termine correctement. Pour ce faire, vous pouvez utiliser du code comme suit :
protected void ObjectDataSource1_Deleted(
object sender, ObjectDataSourceStatusEventArgs e)
{
// If we get back a Boolean value from the DeleteProduct method and it's true,
// then we successfully deleted the product. Set AffectedRows to 1
if (e.ReturnValue is bool && ((bool)e.ReturnValue) == true)
e.AffectedRows = 1;
}
Le code de ces deux gestionnaires d’événements se trouve dans la classe code-behind de l’exemple EfficientPaging.aspx
.
Comparaison des performances de la pagination par défaut et personnalisée
Étant donné que la pagination personnalisée récupère uniquement les enregistrements nécessaires, tandis que la pagination par défaut retourne tous les enregistrements pour chaque page en cours d’affichage, il est clair que la pagination personnalisée est plus efficace que la pagination par défaut. Mais combien plus efficace est la pagination personnalisée ? Quels sont les gains de performances visibles en passant de la pagination par défaut à la pagination personnalisée ?
Malheureusement, il n’y a aucune taille qui correspond à toutes les réponses ici. Le gain de performances dépend d’un certain nombre de facteurs, les deux principaux étant le nombre d’enregistrements paginés et la charge placée sur le serveur de base de données et les canaux de communication entre le serveur web et le serveur de base de données. Pour les petites tables avec seulement quelques dizaines d’enregistrements, la différence de performances peut être négligeable. Pour les grandes tables, avec des milliers à des centaines de milliers de lignes, cependant, la différence de performances est aiguë.
Un article de mine , « Pagination personnalisée dans ASP.NET 2.0 avec SQL Server 2005 », contient certains tests de performances que j’ai exécutés pour présenter les différences de performances entre ces deux techniques de pagination lors de la pagination dans une table de base de données avec 50 000 enregistrements. Dans ces tests, j’ai examiné le temps d’exécuter la requête au niveau SQL Server (à l’aide de SQL Profiler) et sur la page ASP.NET à l’aide des fonctionnalités de traçage de ASP.NET. Gardez à l’esprit que ces tests ont été exécutés sur ma zone de développement avec un seul utilisateur actif, et par conséquent sont non fiables et n’imitent pas les modèles de chargement de site web classiques. Quels que soient les résultats, les résultats illustrent les différences relatives dans le temps d’exécution pour la pagination par défaut et personnalisée lors de l’utilisation de quantités suffisamment importantes de données.
Moyenne. Durée (s) | Reads | |
---|---|---|
Pagination par défaut du profileur SQL | 1.411 | 383 |
Profileur SQL de pagination personnalisé | 0.002 | 29 |
Pagination par défaut ASP.NET trace | 2.379 | N/A |
Pagination personnalisée ASP.NET Trace | 0,029 | N/A |
Comme vous pouvez le voir, la récupération d’une page particulière de données nécessite 354 lectures inférieures en moyenne et terminées en une fraction du temps. Sur la page ASP.NET, la page personnalisée a pu s’afficher près du 1/100 du temps nécessaire lors de l’utilisation de la pagination par défaut.
Résumé
La pagination par défaut est un cinch pour implémenter uniquement la case à cocher Activer la pagination dans la balise active du contrôle Web de données, mais cette simplicité est fournie au coût des performances. Avec la pagination par défaut, lorsqu’un utilisateur demande n’importe quelle page de données , tous les enregistrements sont retournés, même si une petite fraction d’entre eux peut être affichée. Pour lutter contre cette surcharge de performances, ObjectDataSource offre une autre option de pagination personnalisée.
Bien que la pagination personnalisée s’améliore lors des problèmes de performances de pagination par défaut en récupérant uniquement les enregistrements qui doivent être affichés, il est plus impliqué pour implémenter la pagination personnalisée. Tout d’abord, une requête doit être écrite qui accède correctement (et efficacement) au sous-ensemble spécifique d’enregistrements demandés. Cela peut être accompli de plusieurs façons ; celle que nous avons examinée dans ce tutoriel consiste à utiliser la nouvelle ROW_NUMBER()
fonction de SQL Server 2005 pour classer les résultats, puis à retourner uniquement ces résultats dont le classement se situe dans une plage spécifiée. En outre, nous devons ajouter un moyen pour déterminer le nombre total d’enregistrements paginés. Après avoir créé ces méthodes DAL et BLL, nous devons également configurer ObjectDataSource afin qu’il puisse déterminer le nombre total d’enregistrements en cours de pagination et passer correctement les valeurs d’index de ligne de début et de nombre maximal de lignes à la BLL.
Bien que l’implémentation de la pagination personnalisée nécessite un certain nombre d’étapes et n’est pas aussi simple que la pagination par défaut, la pagination personnalisée est une nécessité lors de la pagination de suffisamment grandes quantités de données. Comme l’ont montré les résultats examinés, la pagination personnalisée peut perdre des secondes du temps de rendu de la page ASP.NET et peut alléger la charge sur le serveur de base de données par un ou plusieurs ordres de grandeur.
Bonne programmation !
À propos de l’auteur
Scott Mitchell, auteur de sept livres ASP/ASP.NET et fondateur de 4GuysFromRolla.com, travaille avec les technologies Web Microsoft depuis 1998. Scott travaille en tant que consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 en 24 heures. Il peut être accessible à mitchell@4GuysFromRolla.com. ou via son blog, qui peut être trouvé à http://ScottOnWriting.NET.