Suddivisione in pagine efficiente di grandi quantità di dati (C#)

di Scott Mitchell

Scarica il PDF

L'opzione di paging predefinita di un controllo presentazione dati non è adatta quando si utilizzano grandi quantità di dati, perché il controllo origine dati sottostante recupera tutti i record, anche se viene visualizzato solo un subset di dati. In tali circostanze, è necessario passare al paging personalizzato.

Introduzione

Come illustrato nell'esercitazione precedente, il paging può essere implementato in uno dei due modi seguenti:

  • Il paging predefinito può essere implementato semplicemente selezionando l'opzione Abilita paging nello smart tag del controllo Web dei dati; Tuttavia, ogni volta che si visualizza una pagina di dati, ObjectDataSource recupera tutti i record, anche se nella pagina viene visualizzato solo un subset di dati
  • Il paging personalizzato migliora le prestazioni del paging predefinito recuperando solo i record dal database che devono essere visualizzati per la pagina specifica dei dati richiesti dall'utente; Tuttavia, il paging personalizzato comporta un po 'più sforzo per implementare il paging predefinito

A causa della facilità di implementazione basta selezionare una casella di controllo e si è fatto di nuovo! il paging predefinito è un'opzione interessante. Il suo approccio ingenuo nel recupero di tutti i record, tuttavia, lo rende una scelta implausibile quando si esegue il paging attraverso quantità sufficientemente elevate di dati o per siti con molti utenti simultanei. In tali circostanze, è necessario passare al paging personalizzato per fornire un sistema reattivo.

La sfida del paging personalizzato consiste nel scrivere una query che restituisce il set preciso di record necessari per una determinata pagina di dati. Fortunatamente, Microsoft SQL Server 2005 fornisce una nuova parola chiave per i risultati di classificazione, che consente di scrivere una query in grado di recuperare in modo efficiente il subset di record appropriato. In questa esercitazione verrà illustrato come usare questa nuova parola chiave SQL Server 2005 per implementare il paging personalizzato in un controllo GridView. Anche se l'interfaccia utente per il paging personalizzato è identica a quella per il paging predefinito, l'esecuzione di un'istruzione da una pagina all'altra tramite il paging personalizzato può essere di diversi ordini di grandezza più veloci rispetto al paging predefinito.

Nota

Il miglioramento esatto delle prestazioni esposto dal paging personalizzato dipende dal numero totale di record sottoposti a paging e dal carico inserito nel server di database. Alla fine di questa esercitazione verranno esaminate alcune metriche approssimative che illustrano i vantaggi delle prestazioni ottenute tramite il paging personalizzato.

Passaggio 1: Informazioni sul processo di paging personalizzato

Quando si esegue il paging dei dati, i record precisi visualizzati in una pagina dipendono dalla pagina dei dati richiesti e dal numero di record visualizzati per pagina. Si supponga, ad esempio, di voler scorrere i 81 prodotti, visualizzando 10 prodotti per pagina. Quando si visualizza la prima pagina, si vogliono prodotti da 1 a 10; quando visualizziamo la seconda pagina saremmo interessati a prodotti da 11 a 20 e così via.

Esistono tre variabili che determinano quali record devono essere recuperati e come deve essere eseguito il rendering dell'interfaccia di paging:

  • Start Row Index l'indice della prima riga nella pagina dei dati da visualizzare; questo indice può essere calcolato moltiplicando l'indice di pagina per i record da visualizzare per pagina e aggiungendo uno. Ad esempio, quando si esegue il paging tra record 10 alla volta, per la prima pagina (il cui indice di pagina è 0), l'indice di riga iniziale è 0 * 10 + 1 o 1; per la seconda pagina (il cui indice di pagina è 1), l'indice riga iniziale è 1 * 10 + 1 o 11.
  • Numero massimo di righe il numero massimo di record da visualizzare per pagina. Questa variabile viene definita righe massime perché per l'ultima pagina potrebbero essere restituiti meno record rispetto alle dimensioni della pagina. Ad esempio, quando si esegue il paging tra 81 prodotti 10 record per pagina, la nona e la pagina finale avranno un solo record. Nessuna pagina, tuttavia, mostrerà più record rispetto al valore Massimo righe.
  • Total Record Count il numero totale di record di cui viene eseguito il paging. Anche se questa variabile non è necessaria per determinare quali record recuperare per una determinata pagina, determina l'interfaccia di paging. Ad esempio, se sono presenti 81 prodotti sottoposti a paging, l'interfaccia di paging sa visualizzare nove numeri di pagina nell'interfaccia utente di paging.

Con il paging predefinito, l'indice riga iniziale viene calcolato come prodotto dell'indice di pagina e le dimensioni della pagina più uno, mentre le dimensioni massime delle righe sono semplicemente le dimensioni della pagina. Poiché il paging predefinito recupera tutti i record dal database quando si esegue il rendering di una pagina di dati, l'indice per ogni riga è noto, rendendo così semplice il passaggio alla riga Avvia indice di riga. Inoltre, il conteggio record totale è facilmente disponibile, poiché è semplicemente il numero di record in DataTable (o qualsiasi oggetto utilizzato per contenere i risultati del database).

Date le variabili Start Row Index e Maximum Rows, un'implementazione di paging personalizzata deve restituire solo il subset preciso di record a partire dall'indice di riga iniziale e fino al numero massimo di righe successive. Il paging personalizzato offre due sfide:

  • È necessario essere in grado di associare in modo efficiente un indice di riga a ogni riga di tutti i dati sottoposti a paging, in modo da poter iniziare a restituire record in corrispondenza dell'indice di riga iniziale specificato
  • È necessario fornire il numero totale di record sottoposti a paging

Nei due passaggi successivi verrà esaminato lo script SQL necessario per rispondere a queste due sfide. Oltre allo script SQL, sarà necessario implementare anche i metodi in DAL e BLL.

Passaggio 2: Restituzione del numero totale di record sottoposti a paging

Prima di esaminare come recuperare il sottoinsieme preciso di record per la pagina visualizzata, si esaminerà innanzitutto come restituire il numero totale di record di cui viene eseguito il paging. Queste informazioni sono necessarie per configurare correttamente l'interfaccia utente di paging. Il numero totale di record restituiti da una determinata query SQL può essere ottenuto usando la COUNT funzione di aggregazione. Ad esempio, per determinare il numero totale di record nella Products tabella, è possibile usare la query seguente:

SELECT COUNT(*)
FROM Products

Aggiungere un metodo a DAL che restituisce queste informazioni. In particolare, verrà creato un metodo DAL denominato TotalNumberOfProducts() che esegue l'istruzione SELECT illustrata in precedenza.

Per iniziare, aprire il Northwind.xsd file DataSet tipizzato nella App_Code/DAL cartella . Fare quindi clic con il ProductsTableAdapter pulsante destro del mouse sul nella Designer e scegliere Aggiungi query. Come illustrato nelle esercitazioni precedenti, ciò consentirà di aggiungere un nuovo metodo a DAL che, quando richiamato, eseguirà una particolare istruzione SQL o stored procedure. Come per i metodi TableAdapter nelle esercitazioni precedenti, per questo scegliere di usare un'istruzione SQL ad hoc.

Usare un'istruzione SQL ad hoc

Figura 1: Usare un'istruzione SQL ad hoc

Nella schermata successiva è possibile specificare il tipo di query da creare. Poiché questa query restituirà un singolo valore scalare, il numero totale di record nella Products tabella sceglie l'opzione SELECT che restituisce un valore singe.

Configurare la query per l'utilizzo di un'istruzione SELECT che restituisce un singolo valore

Figura 2: Configurare la query per l'uso di un'istruzione SELECT che restituisce un singolo valore

Dopo aver indicato il tipo di query da usare, è necessario specificare successivamente la query.

Usare la query SELECT COUNT(*) FROM Products

Figura 3: Usare la query SELECT COUNT(*) FROM Products

Infine, specificare il nome per il metodo . Come accennato in precedenza, è possibile usare TotalNumberOfProducts.

Denominare il metodo DAL TotalNumberOfProducts

Figura 4: Denominare il metodo DAL TotalNumberOfProducts

Dopo aver fatto clic su Fine, la procedura guidata aggiungerà il TotalNumberOfProducts metodo a DAL. I metodi scalari che restituiscono i tipi nullable dal, nel caso in cui il risultato della query SQL sia NULL. La COUNT query restituirà tuttavia sempre un valore nonNULL . Indipendentemente dal fatto che il metodo DAL restituisca un numero intero nullable.

Oltre al metodo DAL, è necessario anche un metodo nel BLL. Aprire il file di ProductsBLL classe e aggiungere un TotalNumberOfProducts metodo che chiama semplicemente al metodo dal TotalNumberOfProducts :

public int TotalNumberOfProducts()
{
    return Adapter.TotalNumberOfProducts().GetValueOrDefault();
}

Il metodo DAL s TotalNumberOfProducts restituisce un numero intero nullable. È stato tuttavia creato il ProductsBLL metodo della TotalNumberOfProducts classe in modo che restituisca un numero intero standard. È quindi necessario che il ProductsBLL metodo della TotalNumberOfProducts classe restituisca la parte del valore dell'intero nullable restituito dal metodo DAL.TotalNumberOfProducts La chiamata a GetValueOrDefault() restituisce il valore dell'intero nullable, se esistente; se l'intero nullable è null, tuttavia, restituisce il valore intero predefinito 0.

Passaggio 3: Restituzione del subset preciso di record

L'attività successiva consiste nel creare metodi in DAL e BLL che accettano le variabili Start Row Index e Maximum Rows descritte in precedenza e restituiscono i record appropriati. Prima di eseguire questa operazione, esaminare prima di tutto lo script SQL necessario. La sfida è che dobbiamo essere in grado di assegnare in modo efficiente un indice a ogni riga di tutti i risultati sottoposti a paging, in modo da poter restituire solo i record a partire dall'indice di riga iniziale (e fino al numero massimo di record).

Questo non è un problema se esiste già una colonna nella tabella di database che funge da indice di riga. A prima vista si potrebbe pensare che il Products campo della ProductID tabella sarebbe sufficiente, come il primo prodotto ha ProductID di 1, il secondo un 2 e così via. Tuttavia, l'eliminazione di un prodotto lascia una distanza nella sequenza, annullando questo approccio.

Esistono due tecniche generali usate per associare in modo efficiente un indice di riga ai dati da scorrere, consentendo così il sottoinsieme preciso di record da recuperare:

  • Usando SQL Server 2005 s ROW_NUMBER() Keyword new to SQL Server 2005, la ROW_NUMBER() parola chiave associa una classificazione a ogni record restituito in base ad alcuni ordini. Questa classificazione può essere usata come indice di riga per ogni riga.

  • Uso di una variabile di tabella e SET ROWCOUNT SQL Server'istruzione s SET ROWCOUNT può essere usata per specificare il numero di record totali che una query deve elaborare prima di terminare; le variabili di tabella sono variabili T-SQL locali che possono contenere dati tabulari, simili a tabelle temporanee. Questo approccio funziona altrettanto bene con Microsoft SQL Server 2005 e SQL Server 2000 (mentre l'approccio ROW_NUMBER() funziona solo con SQL Server 2005).

    L'idea è creare una variabile di tabella con una IDENTITY colonna e colonne per le chiavi primarie della tabella i cui dati vengono sottoposti a paging. Successivamente, il contenuto della tabella i cui dati vengono sottoposti a paging viene sottoposto a dump nella variabile di tabella, associando quindi un indice di riga sequenziale (tramite la IDENTITY colonna) per ogni record nella tabella. Dopo aver popolato la variabile di tabella, è possibile eseguire un'istruzione SELECT nella variabile di tabella unita alla tabella sottostante per estrarre i record specifici. L'istruzione SET ROWCOUNT viene usata per limitare in modo intelligente il numero di record che devono essere scaricati nella variabile di tabella.

    L'efficienza di questo approccio si basa sul numero di pagina richiesto, in quanto al SET ROWCOUNT valore viene assegnato il valore di Indice riga iniziale più le righe massime. Quando si esegue il paging tra pagine con numeri bassi, ad esempio le prime pagine di dati, questo approccio è molto efficiente. Tuttavia, mostra prestazioni di paging predefinite simili al recupero di una pagina vicino alla fine.

Questa esercitazione implementa il paging personalizzato usando la ROW_NUMBER() parola chiave . Per altre informazioni sull'uso della variabile e SET ROWCOUNT della tecnica di tabella, vedere Paging efficiente tramite grandi quantità di dati.

Parola ROW_NUMBER() chiave associata a una classificazione a ogni record restituito su un ordine specifico usando la sintassi seguente:

SELECT columnList,
       ROW_NUMBER() OVER(orderByClause)
FROM TableName

ROW_NUMBER() restituisce un valore numerico che specifica la classificazione per ogni record rispetto all'ordinamento indicato. Ad esempio, per visualizzare la classificazione per ogni prodotto, ordinata dal più costoso al minimo, è possibile usare la query seguente:

SELECT ProductName, UnitPrice,
       ROW_NUMBER() OVER(ORDER BY UnitPrice DESC) AS PriceRank
FROM Products

La figura 5 mostra i risultati della query durante l'esecuzione nella finestra di query in Visual Studio. Si noti che i prodotti vengono ordinati per prezzo, insieme a una classificazione dei prezzi per ogni riga.

La classificazione dei prezzi è inclusa per ogni record restituito

Figura 5: La classificazione dei prezzi è inclusa per ogni record restituito

Nota

ROW_NUMBER()è solo una delle nuove funzioni di classificazione disponibili in SQL Server 2005. Per una discussione più approfondita di , insieme alle altre funzioni di classificazione, leggere Restituzione dei ROW_NUMBER()risultati classificati con Microsoft SQL Server 2005.

Quando si classificano i risultati dalla colonna specificata ORDER BY nella OVER clausola (UnitPricenell'esempio precedente), SQL Server deve ordinare i risultati. Si tratta di un'operazione rapida se è presente un indice cluster sulle colonne, i risultati vengono ordinati da o se esiste un indice di copertura, ma può essere più costoso in caso contrario. Per migliorare le prestazioni per query sufficientemente grandi, è consigliabile aggiungere un indice non cluster per la colonna in base al quale i risultati vengono ordinati da. Vedere Funzioni di classificazione e prestazioni in SQL Server 2005 per un'analisi più dettagliata delle considerazioni sulle prestazioni.

Le informazioni di classificazione restituite da ROW_NUMBER() non possono essere usate direttamente nella WHERE clausola . Tuttavia, una tabella derivata può essere usata per restituire il ROW_NUMBER() risultato, che può quindi essere visualizzato nella WHERE clausola . Ad esempio, la query seguente usa una tabella derivata per restituire le colonne ProductName e UnitPrice, insieme al ROW_NUMBER() risultato e quindi usa una WHERE clausola per restituire solo i prodotti il cui rango di prezzo è compreso tra 11 e 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

Estendendo ulteriormente questo concetto, è possibile usare questo approccio per recuperare una pagina specifica di dati, in base ai valori iniziali di indice riga e righe massime desiderate:

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>)

Nota

Come si vedrà più avanti in questa esercitazione, l'oggetto StartRowIndex fornito da ObjectDataSource viene indicizzato a partire da zero, mentre il ROW_NUMBER() valore restituito da SQL Server 2005 viene indicizzato a partire da 1. Pertanto, la WHERE clausola restituisce tali record in cui PriceRank è strettamente maggiore di StartRowIndex e minore o uguale a StartRowIndex + MaximumRows.

Ora che è stato illustrato come ROW_NUMBER() è possibile usare per recuperare una determinata pagina di dati, in base ai valori Start Row Index e Maximum Rows, è ora necessario implementare questa logica come metodi in DAL e BLL.

Quando si crea questa query, è necessario decidere l'ordinamento in base al quale verranno classificati i risultati; consente di ordinare i prodotti in base al nome in ordine alfabetico. Ciò significa che con l'implementazione personalizzata del paging in questa esercitazione non sarà possibile creare un report a pagina personalizzato, che può essere ordinato anche. Nell'esercitazione successiva, tuttavia, verrà illustrato come è possibile fornire tali funzionalità.

Nella sezione precedente è stato creato il metodo DAL come istruzione SQL ad hoc. Sfortunatamente, il parser T-SQL in Visual Studio usato dalla procedura guidata TableAdapter non piace alla OVER sintassi usata dalla ROW_NUMBER() funzione. Pertanto, è necessario creare questo metodo DAL come stored procedure. Selezionare Esplora server dal menu Visualizza (o premere CTRL+ALT+S) e espandere il NORTHWND.MDF nodo. Per aggiungere una nuova stored procedure, fare clic con il pulsante destro del mouse sul nodo Stored Procedure e scegliere Aggiungi una nuova stored procedure (vedere la figura 6).

Aggiungere una nuova stored procedure per il paging tramite i prodotti

Figura 6: Aggiungere una nuova stored procedure per il paging tramite i prodotti

Questa stored procedure deve accettare due parametri @startRowIndex di input integer e @maximumRows usare la ROW_NUMBER() funzione ordinata dal ProductName campo, restituendo solo quelle righe superiori a quelle specificate @startRowIndex e inferiori o uguali a @startRowIndex + @maximumRow s. Immettere lo script seguente nella nuova stored procedure e quindi fare clic sull'icona Salva per aggiungere la stored procedure al database.

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)

Dopo aver creato la stored procedure, richiedere un momento per testarlo. Fare clic con il pulsante destro del mouse sul nome della GetProductsPaged stored procedure in Esplora server e scegliere l'opzione Esegui. Visual Studio richiederà quindi di specificare i parametri @startRowIndex di input e @maximumRow s (vedere la figura 7). Provare valori diversi ed esaminare i risultati.

Immettere un valore per la classe <span=@startRowIndex e @maximumRows parametri" />

Figura 7: immettere un valore per i @startRowIndex parametri e @maximumRows

Dopo aver scelto questi valori di parametri di input, la finestra Output mostrerà i risultati. La figura 8 mostra i risultati durante il passaggio di 10 per @startRowIndex i parametri e @maximumRows .

I record visualizzati nella seconda pagina dei dati vengono restituiti

Figura 8: i record visualizzati nella seconda pagina dei dati vengono restituiti (fare clic per visualizzare l'immagine a dimensioni complete)

Con questa stored procedure creata, è possibile creare il ProductsTableAdapter metodo. Aprire Typed DataSet, fare clic con il Northwind.xsd pulsante destro del ProductsTableAdaptermouse su e scegliere l'opzione Aggiungi query. Anziché creare la query usando un'istruzione SQL ad hoc, crearla usando una stored procedure esistente.

Creare il metodo DAL usando una stored procedure esistente

Figura 9: Creare il metodo DAL usando una stored procedure esistente

Verrà quindi richiesto di selezionare la stored procedure da richiamare. Selezionare la stored procedure dall'elenco GetProductsPaged a discesa.

Scegliere la stored procedure GetProductsPaged dall'elenco Drop-Down

Figura 10: scegliere la stored procedure GetProductsPaged dall'elenco Drop-Down

La schermata successiva chiede quindi quale tipo di dati viene restituito dalla stored procedure: dati tabulari, un singolo valore o nessun valore. Poiché la GetProductsPaged stored procedure può restituire più record, indica che restituisce dati tabulari.

Indica che la stored procedure restituisce dati tabulari

Figura 11: Indicare che la stored procedure restituisce dati tabulari

Infine, indicare i nomi dei metodi da creare. Come per le esercitazioni precedenti, andare avanti e creare metodi usando la tabella dati Fill a DataTable e Return a DataTable.As with our previous tutorials, go go and create methods using the Fill a DataTable and Return a DataTable. Denominare il primo metodo FillPaged e il secondo GetProductsPaged.

Assegnare un nome ai metodi FillPaged e GetProductsPaged

Figura 12: Assegnare un nome ai metodi FillPaged e GetProductsPaged

Oltre a creare un metodo DAL per restituire una determinata pagina di prodotti, è anche necessario fornire tali funzionalità nel BLL. Come il metodo DAL, il metodo GetProductsPaged di BLL deve accettare due input integer per specificare l'indice di riga start e le righe massime e deve restituire solo i record che rientrano nell'intervallo specificato. Creare un metodo BLL di questo tipo nella classe ProductsBLL che semplicemente chiama in basso il metodo GetProductsPaged di DAL, ad esempio:

[System.ComponentModel.DataObjectMethodAttribute(
    System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsPaged(int startRowIndex, int maximumRows)
{
    return Adapter.GetProductsPaged(startRowIndex, maximumRows);
}

È possibile usare qualsiasi nome per i parametri di input del metodo BLL, ma, come si vedrà brevemente, scegliere di usare startRowIndex e maximumRows salvare da un bit di lavoro aggiuntivo durante la configurazione di un oggetto ObjectDataSource per usare questo metodo.

Passaggio 4: Configurazione di ObjectDataSource per l'uso di paging personalizzato

Con i metodi BLL e DAL per accedere a un determinato subset di record completato, è possibile creare un controllo GridView che pagine i record sottostanti usando il paging personalizzato. Iniziare aprendo la EfficientPaging.aspx pagina nella PagingAndSorting cartella, aggiungere gridView alla pagina e configurarla per usare un nuovo controllo ObjectDataSource. Nelle esercitazioni precedenti è stato spesso configurato ObjectDataSource per usare il ProductsBLL metodo della GetProducts classe. Questa volta, tuttavia, si vuole usare il metodo, poiché il GetProductsPagedGetProducts metodo restituisce tutti i prodotti nel database, mentre GetProductsPaged restituisce solo un determinato subset di record.

Configurare ObjectDataSource per usare il metodo GetProductsPaged della classe ProductsBLL

Figura 13: Configurare ObjectDataSource per usare il metodo GetProductsPaged della classe ProductsBLL

Poiché si sta creando una griglia di sola lettura, è necessario impostare l'elenco a discesa del metodo nelle schede INSERT, UPDATE e DELETE su (Nessuno).

La procedura guidata ObjectDataSource richiede quindi le origini dei valori dei GetProductsPaged parametri di input e maximumRows del startRowIndex metodo. Questi parametri di input verranno effettivamente impostati automaticamente da GridView, quindi lasciare il set di origine su Nessuno e fare clic su Fine.

Lasciare le origini dei parametri di input come None

Figura 14: lasciare le origini dei parametri di input come None

Al termine della procedura guidata ObjectDataSource, GridView conterrà un oggetto BoundField o CheckBoxField per ognuno dei campi dati del prodotto. È possibile personalizzare l'aspetto di GridView come si vede. Ho scelto di visualizzare solo , ProductName, CategoryNameSupplierName, QuantityPerUnite UnitPrice BoundFields. Configurare anche GridView per supportare il paging controllando la casella di controllo Abilita paging nel relativo smart tag. Dopo queste modifiche, il markup dichiarativo GridView e ObjectDataSource dovrebbe essere simile al seguente:

<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>

Se si visita la pagina tramite un browser, tuttavia, GridView non è dove trovare.

GridView non viene visualizzato

Figura 15: GridView non viene visualizzato

GridView è mancante perché ObjectDataSource usa attualmente 0 come valori per entrambi i parametri di GetProductsPagedstartRowIndex input e maximumRows . Di conseguenza, la query SQL risultante non restituisce record e pertanto GridView non viene visualizzata.

Per risolvere questo problema, è necessario configurare ObjectDataSource per usare il paging personalizzato. Questa operazione può essere eseguita nei passaggi seguenti:

  1. Impostare la proprietà trueObjectDataSource su EnablePaging questo indica all'oggetto ObjectDataSource che deve passare ai SelectMethod due parametri aggiuntivi: uno per specificare l'indice riga iniziale () e uno per specificare le righe massime (StartRowIndexParameterNameMaximumRowsParameterName).
  2. Impostare ObjectDataSource s StartRowIndexParameterName e Proprietà Di conseguenza le proprietà e MaximumRowsParameterNameMaximumRowsParameterName indicano i nomi dei parametri di input passati in SelectMethod per scopi di paging personalizzati.StartRowIndexParameterName Per impostazione predefinita, questi nomi di parametri sono startIndexRow e maximumRows, che è il motivo per cui, quando si crea il GetProductsPaged metodo nel BLL, sono stati usati questi valori per i parametri di input. Se si sceglie di usare nomi di parametri diversi per il metodo BLLGetProductsPaged, ad startIndex esempio e maxRows, è necessario impostare di conseguenza le proprietà e MaximumRowsParameterName s StartRowIndexParameterName ObjectDataSource( ad esempio startIndex per e maxRows per StartRowIndexParameterNameMaximumRowsParameterName).
  3. Impostare la proprietà ObjectDataSource s SelectCountMethod sul nome del metodo che restituisce il numero totale di record in fase di pagina (TotalNumberOfProducts) ricordare che il ProductsBLL metodo della TotalNumberOfProducts classe restituisce il numero totale di record da paginare tramite un metodo DAL che esegue una SELECT COUNT(*) FROM Products query. Queste informazioni sono necessarie per ObjectDataSource per eseguire correttamente il rendering dell'interfaccia di paging.
  4. Rimuovere gli startRowIndex elementi e maximumRows<asp:Parameter> dal markup dichiarativo di ObjectDataSource s durante la configurazione di ObjectDataSource tramite la procedura guidata, Visual Studio ha aggiunto automaticamente due <asp:Parameter> elementi per i GetProductsPaged parametri di input del metodo. EnablePaging Impostando su true, questi parametri verranno passati automaticamente. Se vengono visualizzati anche nella sintassi dichiarativa, ObjectDataSource tenterà di passare quattro parametri al GetProductsPaged metodo e due parametri al TotalNumberOfProducts metodo. Se si dimentica di rimuovere questi <asp:Parameter> elementi, quando si visita la pagina tramite un browser si riceverà un messaggio di errore come: ObjectDataSource 'ObjectDataSource1' non è stato possibile trovare un metodo non generico 'TotalNumberOfProducts' con parametri: startRowIndex, maximumRows.

Dopo aver apportato queste modifiche, la sintassi dichiarativa di ObjectDataSource deve essere simile alla seguente:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsPaged" EnablePaging="True"
    SelectCountMethod="TotalNumberOfProducts">
</asp:ObjectDataSource>

Si noti che le EnablePaging proprietà e SelectCountMethod sono state impostate e gli <asp:Parameter> elementi sono stati rimossi. La figura 16 mostra una schermata della Finestra Proprietà dopo aver apportato queste modifiche.

Per usare il paging personalizzato, configurare il controllo ObjectDataSource

Figura 16: Per usare il paging personalizzato, configurare il controllo ObjectDataSource

Dopo aver apportato queste modifiche, visitare questa pagina tramite un browser. Verranno visualizzati 10 prodotti elencati, ordinati alfabeticamente. Esaminare i dati una pagina alla volta. Sebbene non esista alcuna differenza visiva dal punto di vista dell'utente finale tra il paging predefinito e il paging personalizzato, il paging personalizzato di pagine tramite grandi quantità di dati recupera solo i record che devono essere visualizzati per una determinata pagina.

I dati, ordinati in base al nome del prodotto, vengono distribuiti tramite paging personalizzato

Figura 17: I dati, ordinati in base al nome del prodotto, vengono visualizzati tramite paging personalizzato (fare clic per visualizzare l'immagine a dimensione intera)

Nota

Con il paging personalizzato, il valore del conteggio delle pagine restituito da ObjectDataSource SelectCountMethod viene archiviato nello stato di visualizzazione di GridView. Altre variabili GridView, PageIndexEditIndexla raccolta , SelectedIndex, DataKeys e così via vengono archiviate nello stato del controllo, che viene mantenuto indipendentemente dal valore della proprietà di EnableViewState GridView. Poiché il PageCount valore viene salvato in modo permanente tra postback usando lo stato di visualizzazione, quando si usa un'interfaccia di paging che include un collegamento per passare all'ultima pagina, è fondamentale abilitare lo stato di visualizzazione di GridView. Se l'interfaccia di paging non include un collegamento diretto all'ultima pagina, è possibile disabilitare lo stato di visualizzazione.

Facendo clic sull'ultimo collegamento di pagina viene generato un postback e viene indicato a GridView di aggiornarne la PageIndex proprietà. Se si fa clic sull'ultimo collegamento alla pagina, GridView assegna la relativa PageIndex proprietà a un valore minore della relativa PageCount proprietà. Se lo stato di visualizzazione è disabilitato, il PageCount valore viene perso tra i postback e viene PageIndex assegnato invece il valore intero massimo. GridView tenta quindi di determinare l'indice di riga iniziale moltiplicando le PageSize proprietà e PageCount . Ciò comporta un OverflowException dato che il prodotto supera le dimensioni intere massime consentite.

Implementare il paging e l'ordinamento personalizzati

L'implementazione corrente del paging personalizzato richiede che l'ordine in base al quale i dati vengano sottoposti a paging venga specificato in modo statico durante la creazione della GetProductsPaged stored procedure. Tuttavia, è possibile notare che lo smart tag di GridView contiene una casella di controllo Abilita ordinamento oltre all'opzione Abilita paging. Purtroppo, l'aggiunta del supporto per l'ordinamento a GridView con l'implementazione corrente del paging personalizzato consente di ordinare solo i record nella pagina dei dati attualmente visualizzati. Ad esempio, se si configura GridView per supportare anche il paging e quindi, quando si visualizza la prima pagina di dati, ordinare in base al nome del prodotto in ordine decrescente, invertire l'ordine dei prodotti nella pagina 1. Come illustrato nella figura 18, tali elementi mostrano Carnarvon Tigers come primo prodotto durante l'ordinamento alfabetico inverso, che ignora i 71 altri prodotti che vengono dopo Carnarvon Tigers, alfabeticamente; solo i record nella prima pagina vengono considerati nell'ordinamento.

Solo i dati visualizzati nella pagina corrente sono ordinati

Figura 18: Solo i dati visualizzati nella pagina corrente sono ordinati (fare clic per visualizzare l'immagine a dimensione intera)

L'ordinamento si applica solo alla pagina corrente dei dati perché l'ordinamento si verifica dopo che i dati sono stati recuperati dal metodo BLL GetProductsPaged e questo metodo restituisce solo tali record per la pagina specifica. Per implementare correttamente l'ordinamento, è necessario passare l'espressione di ordinamento al GetProductsPaged metodo in modo che i dati possano essere classificati in modo appropriato prima di restituire la pagina di dati specifica. Verrà illustrato come eseguire questa operazione nell'esercitazione successiva.

Implementazione di paging ed eliminazione personalizzati

Se si abilita l'eliminazione di funzionalità in un controllo GridView i cui dati vengono impaginati usando tecniche di paging personalizzate, si scoprirà che quando si elimina l'ultimo record dall'ultima pagina, GridView scompare invece di decrementare in modo appropriato gridView s PageIndex. Per riprodurre questo bug, abilitare l'eliminazione per l'esercitazione appena creata. Passare all'ultima pagina (pagina 9), in cui dovrebbe essere visualizzato un singolo prodotto, dal momento che stiamo paging attraverso 81 prodotti, 10 prodotti alla volta. Eliminare il prodotto.

Quando si elimina l'ultimo prodotto, GridView dovrebbe passare automaticamente all'ottava pagina e tale funzionalità viene visualizzata con il paging predefinito. Con il paging personalizzato, tuttavia, dopo l'eliminazione dell'ultimo prodotto nell'ultima pagina, GridView scompare semplicemente dallo schermo. Il motivo preciso per cui ciò accade è un po' oltre l'ambito di questa esercitazione; vedere Eliminazione dell'ultimo record nell'ultima pagina da un controllo GridView con paging personalizzato per i dettagli di basso livello relativi all'origine di questo problema. In sintesi è dovuto alla sequenza di passaggi seguente eseguiti da GridView quando si fa clic sul pulsante Elimina:

  1. Eliminare il record
  2. Ottenere i record appropriati da visualizzare per l'oggetto specificato PageIndex e PageSize
  3. Verificare che non PageIndex superi il numero di pagine di dati nell'origine dati. In tal caso, decrementare automaticamente la proprietà GridView PageIndex
  4. Associare la pagina di dati appropriata a GridView usando i record ottenuti nel passaggio 2

Il problema deriva dal fatto che nel passaggio 2 l'oggetto PageIndex usato quando si afferrano i record da visualizzare è ancora l'ultima PageIndex pagina il cui unico record è stato appena eliminato. Pertanto, nel passaggio 2 , non vengono restituiti record dall'ultima pagina di dati che non contiene più record. Quindi, nel passaggio 3, GridView si rende conto che la relativa PageIndex proprietà è maggiore del numero totale di pagine nell'origine dati (poiché è stato eliminato l'ultimo record nell'ultima pagina) e quindi decrementa la relativa PageIndex proprietà. Nel passaggio 4 GridView tenta di associarsi ai dati recuperati nel passaggio 2; Tuttavia, nel passaggio 2 non sono stati restituiti record, pertanto viene restituito un controllo GridView vuoto. Con il paging predefinito, questo problema non viene visualizzato perché nel passaggio 2 tutti i record vengono recuperati dall'origine dati.

Per risolvere questo problema, sono disponibili due opzioni. Il primo consiste nel creare un gestore eventi per il gestore eventi di GridView che RowDeleted determina il numero di record visualizzati nella pagina appena eliminata. Se c'era un solo record, il record appena eliminato deve essere stato l'ultimo e dobbiamo decrementare gridView s PageIndex. Naturalmente, si vuole aggiornare PageIndex solo se l'operazione di eliminazione è stata effettivamente eseguita correttamente, che può essere determinata assicurando che la e.Exception proprietà sia null.

Questo approccio funziona perché aggiorna dopo il PageIndex passaggio 1, ma prima del passaggio 2. Di conseguenza, nel passaggio 2 viene restituito il set appropriato di record. A tale scopo, usare codice simile al seguente:

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);
}

Una soluzione alternativa consiste nel creare un gestore eventi per l'evento ObjectDataSource e RowDeleted impostare la AffectedRows proprietà su un valore pari a 1. Dopo aver eliminato il record nel passaggio 1 (ma prima di recuperare nuovamente i dati nel passaggio 2), GridView aggiorna la relativa PageIndex proprietà se una o più righe sono interessate dall'operazione. Tuttavia, la AffectedRows proprietà non è impostata da ObjectDataSource e pertanto questo passaggio viene omesso. Un modo per eseguire questo passaggio consiste nell'impostare manualmente la AffectedRows proprietà se l'operazione di eliminazione viene completata correttamente. Questa operazione può essere eseguita usando codice simile al seguente:

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;
}

Il codice per entrambi questi gestori di eventi è disponibile nella classe code-behind dell'esempio EfficientPaging.aspx .

Confronto delle prestazioni del paging predefinito e personalizzato

Poiché il paging personalizzato recupera solo i record necessari, mentre il paging predefinito restituisce tutti i record per ogni pagina visualizzata, è chiaro che il paging personalizzato è più efficiente del paging predefinito. Ma quanto più efficiente è il paging personalizzato? Quale tipo di miglioramento delle prestazioni è possibile osservare passando dal paging predefinito al paging personalizzato?

Sfortunatamente, non c'è una dimensione adatta a tutte le risposte qui. Il miglioramento delle prestazioni dipende da diversi fattori, i due più importanti sono il numero di record sottoposti a paging e il carico inserito nel server di database e nei canali di comunicazione tra il server Web e il server di database. Per le tabelle di piccole dimensioni con solo poche decine di record, la differenza di prestazioni può essere trascurabile. Per le tabelle di grandi dimensioni, con migliaia di migliaia di righe, tuttavia, la differenza di prestazioni è acuta.

Un articolo del mio, "Paging personalizzato in ASP.NET 2.0 con SQL Server 2005", contiene alcuni test delle prestazioni eseguiti per mostrare le differenze nelle prestazioni tra queste due tecniche di paging durante il paging in una tabella di database con 50.000 record. In questi test sono stati esaminati sia il tempo necessario per eseguire la query a livello di SQL Server (usando SQL Profiler) sia nella pagina ASP.NET usando le funzionalità di traccia di ASP.NET. Tenere presente che questi test sono stati eseguiti nella casella di sviluppo con un singolo utente attivo e pertanto non sono scientifici e non simulano i modelli di carico tipici del sito Web. Indipendentemente dai risultati, i risultati illustrano le differenze relative nel tempo di esecuzione per il paging predefinito e personalizzato quando si utilizzano quantità di dati sufficientemente grandi.

Durata media (sec) Reads
Paging predefinito di SQL Profiler 1.411 383
Profiler SQL di paging personalizzato 0.002 29
Paging predefinito ASP.NET traccia 2.379 N/D
Traccia ASP.NET paging personalizzata 0.029 N/D

Come si può notare, il recupero di una determinata pagina di dati richiede 354 letture in media e completate in una frazione del tempo. Nella pagina ASP.NET, la pagina personalizzata è stata in grado di eseguire il rendering in prossimità del 1/100del tempo impiegato quando si usa il paging predefinito.

Riepilogo

Il paging predefinito è un cinch per implementare solo selezionare la casella di controllo Abilita paging nello smart tag del controllo Web dei dati, ma tale semplicità comporta il costo delle prestazioni. Con il paging predefinito, quando un utente richiede qualsiasi pagina di dati, vengono restituiti tutti i record, anche se è possibile visualizzare solo una piccola frazione di essi. Per contrastare questo sovraccarico delle prestazioni, ObjectDataSource offre un'opzione di paging alternativa di paging personalizzata.

Anche se il paging personalizzato migliora i problemi di prestazioni del paging predefinito recuperando solo i record che devono essere visualizzati, è più necessario implementare il paging personalizzato. Prima di tutto, è necessario scrivere una query che acceda correttamente (ed in modo efficiente) al subset specifico di record richiesti. Questa operazione può essere eseguita in diversi modi; quello esaminato in questa esercitazione consiste nell'usare SQL Server 2005 s nuova ROW_NUMBER() funzione per classificare i risultati e quindi restituire solo i risultati la cui classificazione rientra in un intervallo specificato. Inoltre, è necessario aggiungere un mezzo per determinare il numero totale di record da scorrere. Dopo aver creato questi metodi DAL e BLL, è anche necessario configurare ObjectDataSource in modo che possa determinare il numero di record totali sottoposti a paging e passare correttamente i valori Start Row Index e Maximum Rows al BLL.

Anche se l'implementazione del paging personalizzato richiede diversi passaggi e non è quasi altrettanto semplice quanto il paging predefinito, il paging personalizzato è una necessità quando si esegue il paging in quantità di dati sufficientemente grandi. Come illustrato dai risultati esaminati, il paging personalizzato può liberare i secondi dal tempo di rendering della pagina ASP.NET e può rendere più leggero il carico sul server di database di un'ora più ordini di grandezza.

Buon programmatori!

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, lavora con le tecnologie Web Microsoft dal 1998. Scott lavora come consulente indipendente, formatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2.0 in 24 ore. Può essere raggiunto all'indirizzo mitchell@4GuysFromRolla.com. o tramite il suo blog, disponibile all'indirizzo http://ScottOnWriting.NET.