Condividi tramite


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

di Scott Mitchell

Scarica PDF

L'opzione di paging predefinita di un controllo presentazione dati non è adatta quando si utilizzano grandi quantità di dati, poiché 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 sottoinsieme.
  • 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 è sufficiente 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 di grandi quantità di dati o per siti con molti utenti simultanei. In tali circostanze, è necessario rivolgersi 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 sottoinsieme appropriato di record. In questa esercitazione verrà illustrato come usare questa nuova parola chiave di 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 a quella successiva tramite il paging personalizzato può essere più veloce 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:

  • Inizio indice riga l'indice della prima riga nella pagina di 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 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 numero massimo di righe poiché per l'ultima pagina potrebbero essere restituiti meno record rispetto alle dimensioni della pagina. Ad esempio, quando si esegue il paging tra i 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 la dimensione massima delle righe è semplicemente la dimensione 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 lo spostamento alla riga Avvia indice di riga. Inoltre, il conteggio totale record è facilmente disponibile, poiché è semplicemente il numero di record in DataTable (o qualsiasi oggetto utilizzato per contenere i risultati del database).

In base alle variabili Start Row Index e Maximum Rows, un'implementazione di paging personalizzata deve restituire solo il sottoinsieme preciso di record a partire dall'indice di riga iniziale e fino al numero massimo di righe di record dopo di esso. 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 i 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 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, è possibile esaminare innanzitutto come restituire il numero totale di record sottoposti a 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 al 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 su in Progettazione e scegliere Aggiungi query. Come illustrato nelle esercitazioni precedenti, questo consentirà di aggiungere un nuovo metodo al dal che, quando richiamato, eseguirà una particolare istruzione SQL o stored procedure. Come per i metodi TableAdapter nelle esercitazioni precedenti, per questo consenso esplicito all'uso di 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: Assegnare un nome al metodo DAL TotalNumberOfProducts

Dopo aver fatto clic su Fine, la procedura guidata aggiungerà il TotalNumberOfProducts metodo al dal. I metodi scalari che restituiscono i tipi nullable restituiti da DAL, nel caso in cui il risultato della query SQL sia NULL. La COUNT query restituirà tuttavia sempre un valore diversoNULL da . Indipendentemente dal metodo DAL restituisce 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 semplicemente chiama il metodo dal TotalNumberOfProducts dal:

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

Il metodo DAL TotalNumberOfProducts restituisce un numero intero nullable. Tuttavia, è stato creato il ProductsBLL metodo della classe in TotalNumberOfProducts modo che restituisca un intero standard. È pertanto 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 lo script SQL necessario. La sfida che ci si presenta è che dobbiamo essere in grado di assegnare in modo efficiente un indice a ogni riga dei 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 nella tabella di database è già presente una colonna 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 1, il secondo un 2 e così via. Tuttavia, l'eliminazione di un prodotto lascia un vuoto nella sequenza, nullizzando questo approccio.

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

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

  • È possibile usare un'istruzione Table Variable e SET ROWCOUNTSQL Server per SET ROWCOUNT 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 alle 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 della 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 da eseguire nel dump nella variabile di tabella.

    L'efficienza di questo approccio si basa sul numero di pagina richiesto, perché 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 per quanto riguarda l'ordinamento indicato. Ad esempio, per visualizzare il rango per ogni prodotto, ordinato 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 di questa query durante l'esecuzione nella finestra di query in Visual Studio. Si noti che i prodotti vengono ordinati in base al prezzo, insieme a un rango di prezzo 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 molte nuove funzioni di classificazione disponibili in SQL Server 2005. Per una descrizione più approfondita di , insieme alle altre funzioni di ROW_NUMBER()classificazione, vedere Restituzione di risultati classificati con Microsoft SQL Server 2005.

Quando si classificano i risultati in base alla colonna specificata nella OVER clausola (UnitPricenell'esempio precedenteORDER BY), SQL Server deve ordinare i risultati. Si tratta di un'operazione rapida se è presente un indice cluster sulle colonne in cui vengono ordinati i risultati oppure se è presente 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 alla quale i risultati vengono ordinati in base a . Per informazioni più dettagliate sulle considerazioni sulle prestazioni, vedere Funzioni di classificazione e prestazioni in SQL Server 2005 .

Le informazioni di classificazione restituite da ROW_NUMBER() non possono essere utilizzate direttamente nella WHERE clausola . Tuttavia, è possibile utilizzare una tabella derivata 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 di indice riga iniziale e massimo righe desiderati:

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 i record in cui PriceRank è rigorosamente maggiore StartRowIndex di e minore o uguale a StartRowIndex + MaximumRows.

Ora che è stato illustrato come ROW_NUMBER() 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 di paging personalizzata in questa esercitazione non sarà possibile creare un report con paging personalizzato che non sarà possibile ordinare. Nell'esercitazione successiva, tuttavia, si vedrà 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 la 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) ed 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 dei prodotti

Figura 6: Aggiungere una nuova stored procedure per il paging dei 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 le righe maggiori dell'oggetto specificato @startRowIndex e minori 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, è necessario attendere qualche minuto per testarla. 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 immettere i @startRowIndex parametri 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 dei parametri di input, nella finestra Output verranno visualizzati i risultati. La figura 8 mostra i risultati quando si passa 10 per entrambi i @startRowIndex parametri e @maximumRows .

Vengono restituiti i record che verrebbero visualizzati nella seconda pagina di dati

Figura 8: Vengono restituiti i record che verrebbero visualizzati nella seconda pagina di dati (fare clic per visualizzare l'immagine a dimensione intera)

Dopo aver creato questa stored procedure, è possibile creare il ProductsTableAdapter metodo . Aprire il Northwind.xsd set di dati tipizzato, fare clic con il pulsante destro del ProductsTableAdaptermouse in 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 GetProductsPaged stored procedure dall'elenco a discesa.

Scegliere la stored procedure GetProductsPaged dall'elenco a discesa

Figura 10: Scegliere la stored procedure GetProductsPaged dall'elenco a discesa

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, indicare che restituisce dati tabulari.

Indicare che la stored procedure restituisce dati tabulari

Figura 11: Indicare che la stored procedure restituisce dati tabulari

Infine, indicare i nomi dei metodi che si desidera creare. Come per le esercitazioni precedenti, procedere e creare metodi usando sia Fill a DataTable che Return a DataTable. Denominare il primo metodo FillPaged e il secondo GetProductsPaged.

Denominare i 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, è necessario fornire tali funzionalità nel BLL. Analogamente al metodo DAL, il metodo GetProductsPaged di BLL deve accettare due input integer per specificare l'indice riga iniziale e le righe massime e devono restituire solo i record che rientrano nell'intervallo specificato. Creare un metodo BLL di questo tipo nella classe ProductsBLL che si limita a chiamare il metodo GetProductsPaged dal, come illustrato di seguito:

[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à a breve, scegliere di usare startRowIndex e maximumRows risparmiare da un bit di lavoro aggiuntivo durante la configurazione di objectDataSource per l'uso di questo metodo.

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

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

Configurare ObjectDataSource per l'utilizzo del metodo GetProductsPaged della classe ProductsBLL

Figura 13: Configurare ObjectDataSource per l'uso del metodo GetProductsPaged della classe ProductsBLL

Poiché si sta creando un controllo GridView di sola lettura, 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 è sufficiente lasciare l'origine impostata su Nessuno e fare clic su Fine.

Lasciare le origini dei parametri di input come Nessuna

Figura 14: Lasciare le origini dei parametri di input come Nessuna

Dopo aver completato la procedura guidata ObjectDataSource, GridView conterrà un oggetto BoundField o CheckBoxField per ognuno dei campi dati del prodotto. È possibile personalizzare l'aspetto di GridView nel modo desiderato. Ho scelto di visualizzare solo i ProductNamecampi , CategoryName, SupplierName, QuantityPerUnite UnitPrice BoundFields. Configurare anche GridView per supportare il paging selezionando la casella di controllo Abilita paging nello 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 visualizzato

GridView non è presente perché ObjectDataSource usa attualmente 0 come valori per entrambi i GetProductsPaged startRowIndex parametri di input e maximumRows . Di conseguenza, la query SQL risultante non restituisce alcun record e pertanto GridView non viene visualizzato.

Per risolvere questo problema, è necessario configurare ObjectDataSource per l'uso del paging personalizzato. Questa operazione può essere eseguita nei passaggi seguenti:

  1. Impostare la proprietà true ObjectDataSource su EnablePaging questo valore indica a ObjectDataSource che deve passare ai SelectMethod due parametri aggiuntivi: uno per specificare l'indice di riga iniziale (StartRowIndexParameterName) e uno per specificare le righe massime (MaximumRowsParameterName).
  2. Impostare ObjectDataSource s StartRowIndexParameterName e Proprietà Di conseguenza le StartRowIndexParameterName proprietà e MaximumRowsParameterName MaximumRowsParameterName indicano i nomi dei parametri di input passati a SelectMethod per scopi di paging personalizzati. Per impostazione predefinita, questi nomi di parametro sono startIndexRow e maximumRows, motivo per cui, quando si crea il GetProductsPaged metodo nel BLL, questi valori sono stati usati per i parametri di input. Se si sceglie di usare nomi di parametro diversi per il metodo BLL, GetProductsPaged ad startIndex esempio e maxRows, è necessario impostare di conseguenza le proprietà e MaximumRowsParameterName di ObjectDataSource , StartRowIndexParameterName ad esempio startIndex per StartRowIndexParameterName e maxRows per MaximumRowsParameterName.
  3. Impostare la proprietà ObjectDataSource s SelectCountMethod sul nome del metodo che restituisce il numero totale di record sottoposti a paging (TotalNumberOfProducts) ricordare che il ProductsBLL metodo della TotalNumberOfProducts classe restituisce il numero totale di record sottoposti a paging 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 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. Impostando EnablePaging 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 verrà visualizzato un messaggio di errore simile a: 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 dovrebbe 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 che gli <asp:Parameter> elementi sono stati rimossi. La figura 16 mostra una schermata del 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. Dovrebbero essere 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 in modo più efficiente 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, la PageIndexraccolta , EditIndexSelectedIndex, DataKeys e così via vengono archiviate in stato di controllo, che vengono mantenute indipendentemente dal valore della proprietà gridViewEnableViewState. 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 di pagina, GridView assegna la relativa PageIndex proprietà a un valore minore della relativa PageCount proprietà. Con 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 dato OverflowException che il prodotto supera le dimensioni massime consentite per numeri interi.

Implementare il paging e l'ordinamento personalizzati

L'implementazione di paging personalizzata corrente 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, potresti aver notato che lo smart tag gridView contiene una casella di controllo Abilita ordinamento oltre all'opzione Abilita paging. Sfortunatamente, l'aggiunta del supporto per l'ordinamento a GridView con l'implementazione di paging personalizzata corrente consente di ordinare solo i record nella pagina di dati attualmente visualizzata. 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, questi mostrano Carnarvon Tigers come primo prodotto quando si ordina in ordine alfabetico inverso, che ignora i 71 altri prodotti che vengono dopo Carnarvon Tigers, alfabeticamente; solo i record nella prima pagina vengono considerati nell'ordinamento.

Vengono ordinati solo i dati visualizzati nella pagina corrente

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

L'ordinamento si applica solo alla pagina di dati corrente perché l'ordinamento si verifica dopo che i dati sono stati recuperati dal metodo BLL e GetProductsPaged questo metodo restituisce solo i 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 la funzionalità di eliminazione in un controllo GridView i cui dati vengono inseriti in pagine usando tecniche di paging personalizzate, si scopre 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. Vai all'ultima pagina (pagina 9), dove dovresti vedere un singolo prodotto, perché stiamo paging attraverso 81 prodotti, 10 prodotti alla volta. Elimina questo prodotto.

Dopo l'eliminazione dell'ultimo prodotto, GridView dovrebbe passare automaticamente all'ottava pagina e tale funzionalità viene visualizzata con il paging predefinito. Con il paging personalizzato, tuttavia, dopo aver eliminato l'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 come origine di questo problema. In sintesi, è dovuto alla sequenza di passaggi seguente che vengono 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 caso affermativo, 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 dopo l'ultima pagina di dati 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 ne decrementa PageIndex la 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 RowDeleted GridView che determina il numero di record visualizzati nella pagina appena eliminata. Se è presente un solo record, il record appena eliminato deve essere stato l'ultimo ed è necessario decrementare gridView s PageIndex. Naturalmente, si vuole aggiornare solo PageIndex 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. Pertanto, 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 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 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 rispetto al paging predefinito. Ma quanto è più efficiente il paging personalizzato? Quale tipo di miglioramento delle prestazioni può essere visto 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 sul server di database e sui 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 a centinaia di migliaia di righe, tuttavia, la differenza di prestazioni è acuta.

Un articolo del mio articolo, "Paging personalizzato in ASP.NET 2.0 con SQL Server 2005", contiene alcuni test delle prestazioni eseguiti per mostrare le differenze di 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, quindi non sono scientifici e non simulano 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
Paging personalizzato ASP.NET traccia 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 necessario quando si usa il paging predefinito.

Riepilogo

Il paging predefinito è un cinch per implementare è sufficiente 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 combattere questo sovraccarico delle prestazioni, ObjectDataSource offre un'opzione di paging alternativa 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, una query deve essere scritta in modo corretto (ed efficiente) accede al subset specifico di record richiesti. Questa operazione può essere eseguita in diversi modi; quello esaminato in questa esercitazione consiste nell'usare la nuova ROW_NUMBER() funzione di SQL Server 2005 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 sottoposti a paging. Dopo aver creato questi metodi DAL e BLL, è anche necessario configurare ObjectDataSource in modo che possa determinare il numero di record totali di cui viene eseguito il paging e passare correttamente i valori Inizio indice riga e Righe massime al BLL.

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

Buon programmatori!

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, ha lavorato 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, che può essere trovato all'indirizzo http://ScottOnWriting.NET.