Dela via


Effektivt bläddra igenom stora mängder data (C#)

av Scott Mitchell

Ladda ned PDF

Standardalternativet för paginering i en datavisningskontroll är olämpligt när du arbetar med stora mängder data, eftersom dess underliggande datakällskontroll hämtar alla poster, även om endast en delmängd av data visas. I sådana fall måste vi vända oss till anpassad sidladdning.

Inledning

Som vi diskuterade i föregående handledning kan paging implementeras på något av två sätt:

  • Standardvisning kan implementeras genom att enkelt markera alternativet Aktivera sidindelning i datawebbkontrollens smarta tagg. Dock när en sida med data visas hämtar ObjectDataSource alla poster, även om endast en delmängd av dem visas på sidan.
  • Anpassad växling förbättrar prestandan för standardväxling genom att endast hämta de poster från databasen som måste visas för den specifika sidan med data som begärs av användaren. Anpassad växling innebär dock lite mer arbete att implementera än standardväxling

På grund av den enkla implementeringen markerar du bara en kryssruta så är du klar! Förvald paginering är ett attraktivt alternativ. Dess naiva metod för att hämta alla poster gör det dock till ett osannolikt val när du söker igenom tillräckligt stora mängder data eller för webbplatser med många samtidiga användare. I sådana fall måste vi vända oss till anpassad sidindelning för att tillhandahålla ett responsivt system.

Utmaningen med anpassad sidhämtning är att kunna skriva en fråga som returnerar den exakta resultatuppsättningen av poster som behövs för en viss datasida. Lyckligtvis tillhandahåller Microsoft SQL Server 2005 ett nytt nyckelord för rankningsresultat, vilket gör att vi kan skriva en fråga som effektivt kan hämta rätt delmängd av poster. I den här handledningen ska vi se hur man använder det nya nyckelordet för SQL Server 2005 för att implementera anpassad paginering i en GridView-kontroll. Även om användargränssnittet för anpassad växling är identiskt med det för standardväxling, kan steg från en sida till en annan med anpassad växling vara flera storleksordningar snabbare än standardväxling.

Anmärkning

Den exakta prestandavinst som uppnås genom anpassad sidindelning beror på det totala antalet poster som bläddras igenom och belastningen på databasservern. I slutet av den här självstudien tittar vi på några grova indikatorer som visar prestandafördelarna erhållna genom anpassad sidindelning.

Steg 1: Förstå den anpassade sidindelningsprocessen

Vid bläddring i data beror de exakta poster som visas på en sida på data-sidan som efterfrågas och antalet poster som visas per sida. Anta till exempel att vi ville gå igenom de 81 produkterna och visa 10 produkter per sida. När du visar den första sidan vill vi ha produkter 1 till 10; när vi visar den andra sidan skulle vi vara intresserade av produkter 11 till 20, och så vidare.

Det finns tre variabler som avgör vilka poster som behöver hämtas och hur sidorna ska visas.

  • Startindex för rad index för den första raden på sidan med data som ska visas; det här indexet kan beräknas genom att multiplicera sidindexet med antalet poster som ska visas per sida och lägga till ett. När du till exempel bläddrar genom poster 10 åt gången, är startindex för rad för den första sidan (vars sidindex är 0) 0 * 10 + 1, eller 1; för den andra sidan (vars sidindex är 1) är startindex för rad 1 * 10 + 1, eller 11.
  • Maximalt antal rader det maximala antalet poster som ska visas per sida. Den här variabeln kallas maximala rader eftersom det för den sista sidan kan finnas färre poster som returneras än sidans storlek. När du till exempel bläddrar igenom 81 produkter, med 10 poster per sida, kommer den nionde och sista sidan att ha bara en post. Ingen sida visar dock fler poster än värdet Maximalt antal rader.
  • Totalt antal poster det totala antalet poster som bläddras igenom. Även om den här variabeln inte behövs för att avgöra vilka poster som ska hämtas för en viss sida, dikterar den sidindelningsgränssnittet. Om det till exempel finns 81 produkter som bläddras igenom vet paginagränssnittet att nio sidnummer visas.

Med standardpaginering beräknas Startkorrektionsindex som produkten av sidindexet och sidstorleken, plus ett, medan Maximalt antal rader är helt enkelt sidstorleken. Eftersom standardpaginering hämtar alla poster från databasen när du visar en sida med data, är indexet för varje rad känt, vilket gör det till en trivial uppgift att flytta till Start Radsindex. Dessutom är det totala antalet poster lättillgängligt, eftersom det bara är antalet poster i DataTable (eller det objekt som används för att lagra databasresultaten).

Med tanke på variablerna Startradsindex och Maximalt antal rader, måste en anpassad paging-implementering endast returnera den exakta delmängden av poster som börjar vid Startradsindex och sträcker sig över upp till Maximalt antal rader efter det. Anpassad sidhantering innebär två utmaningar:

  • Vi måste effektivt kunna associera ett radindex med varje rad i hela data som bläddras igenom så att vi kan börja returnera poster i det angivna startradsindexet
  • Vi måste ange det totala antalet poster som bläddras igenom

I de kommande två stegen ska vi undersöka sql-skriptet som behövs för att svara på dessa två utmaningar. Förutom SQL-skriptet behöver vi även implementera metoder i DAL och BLL.

Steg 2: Returnera det totala antalet poster som bläddras igenom

Innan vi undersöker hur du hämtar den exakta delmängden av poster för sidan som visas ska vi först titta på hur du returnerar det totala antalet poster som bläddras igenom. Den här informationen behövs för att korrekt konfigurera sidorhanteringsgränssnittet. Det totala antalet poster som returneras av en viss SQL-fråga kan hämtas med hjälp av aggregeringsfunktionenCOUNT. För att till exempel fastställa det totala antalet poster i Products tabellen kan vi använda följande fråga:

SELECT COUNT(*)
FROM Products

Nu ska vi lägga till en metod i vår DAL som returnerar den här informationen. I synnerhet skapar vi en DAL-metod med namnet TotalNumberOfProducts() som kör instruktionen SELECT som visas ovan.

Börja med att Northwind.xsd öppna filen Typed DataSet i App_Code/DAL mappen . Högerklicka sedan på ProductsTableAdapter i designern och välj Lägg till fråga. Som vi har sett i tidigare självstudier kan vi lägga till en ny metod i DAL som, när den anropas, kör en viss SQL-instruktion eller lagrad procedur. Precis som med våra TableAdapter-metoder i de tidigare självstudierna, välj att använda en ad hoc SQL-instruktion för denna.

Använd en ad hoc SQL-instruktion

Bild 1: Använd en ad hoc SQL-instruktion

På nästa skärm kan vi ange vilken typ av fråga som ska skapas. Eftersom den här frågan kommer att returnera ett enda, skalärt värde - det totala antalet poster i Products-tabellen - välj alternativet i SELECT som returnerar enstaka värde.

Konfigurera frågan så att den använder en SELECT-instruktion som returnerar ett enskilt värde

Bild 2: Konfigurera frågan så att den använder en SELECT-instruktion som returnerar ett enskilt värde

När vi har angett vilken typ av fråga som ska användas måste vi sedan ange frågan.

Använd frågan SELECT COUNT(*) FROM Products

Bild 3: Använd SELECT COUNT(*) FROM Products-fråga

Ange slutligen namnet på metoden. Som nämnts ovan ska vi använda TotalNumberOfProducts.

Namnge DAL-metoden TotalNumberOfProducts

Bild 4: Ge namnet DAL-metoden TotalNumberOfProducts

När du har klickat på Slutför lägger guiden till TotalNumberOfProducts metoden i DAL. De skalärre returnerade metoderna i DAL returnerar nullbara typer, om resultatet från SQL-frågan är NULL. Vår COUNT fråga returnerar dock alltid ett icke-värde. Oavsett returnerar DAL-metodenNULL ett heltal som kan vara null.

Förutom DAL-metoden behöver vi också en metod i BLL:n. ProductsBLL Öppna klassfilen och lägg till en TotalNumberOfProducts metod som helt enkelt anropar dal-metodenTotalNumberOfProducts:

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

METODEN DAL s TotalNumberOfProducts returnerar ett null-heltal, men vi har skapat ProductsBLL metoden class s TotalNumberOfProducts så att den returnerar ett standard heltal. Därför måste class s ProductsBLL metoden returnera värdedelen av det nullable heltal som returneras av DAL s TotalNumberOfProducts metoden. Anropet till GetValueOrDefault() returnerar värdet för det nullbara heltalet, om det finns. Om det nullbara heltalet är nullreturneras dock standard heltalsvärdet 0.

Steg 3: Returnera den precisa delmängden av dataposter

Nästa uppgift är att skapa metoder i DAL och BLL som accepterar variablerna Start Row Index och Maximum Rows som beskrevs tidigare och returnerar lämpliga poster. Innan vi gör det ska vi först titta på det NÖDVÄNDIGA SQL-skriptet. Utmaningen vi står inför är att vi effektivt måste kunna tilldela ett index till varje rad i hela resultatet som genomgår paginering, så att vi bara kan returnera de poster som börjar vid Start Row Index (och upp till det maximalt angivna antalet poster).

Detta är inte en utmaning om det redan finns en kolumn i databastabellen som fungerar som ett radindex. Vid första anblicken kanske vi tror att Products tabellens ProductID fält skulle räcka, eftersom den första produkten har ProductID 1, den andra en 2 och så vidare. Att ta bort en produkt lämnar dock en lucka i sekvensen, vilket upphäver den här metoden.

Det finns två allmänna metoder som används för att effektivt associera ett radindex med data att bläddra igenom, vilket gör att den exakta delmängden av poster kan hämtas:

  • Använda SQL Server 2005 s ROW_NUMBER() NyckelordetROW_NUMBER() är nytt för SQL Server 2005 och associerar en rangordning med varje returnerad post baserat på viss ordning. Den här rangordningen kan användas som ett radindex för varje rad.

  • Använda en tabellvariabel och SET ROWCOUNT SQL Server-instruktionens SET ROWCOUNT uttalande kan användas för att specificera hur många totala poster en fråga ska bearbeta innan den avslutas. Tabellvariabler är lokala T-SQL-variabler som kan hålla tabelldata, liknande temporära tabeller. Den här metoden fungerar lika bra med både Microsoft SQL Server 2005 och SQL Server 2000 (medan ROW_NUMBER() metoden endast fungerar med SQL Server 2005).

    Tanken här är att skapa en tabellvariabel som har en IDENTITY kolumn och kolumner för de primära nycklarna i tabellen vars data bläddras igenom. Därefter dumpas innehållet i tabellen vars data bläddras igenom i tabellvariabeln, vilket associerar ett sekventiellt radindex (via IDENTITY kolumnen) för varje post i tabellen. När tabellvariabeln har fyllts i kan en SELECT instruktion i tabellvariabeln, som är kopplad till den underliggande tabellen, köras för att hämta de specifika posterna. -instruktionen SET ROWCOUNT används för att intelligent begränsa antalet poster som behöver dumpas i tabellvariabeln.

    Den här metodens effektivitet baseras på det sidnummer som begärs, eftersom SET ROWCOUNT-värdet tilldelas värdet av startindex för rad plus det maximala antalet rader. När du går igenom lågnumrerade sidor, till exempel de första sidorna med data, är den här metoden mycket effektiv. Den uppvisar dock standardprestanda liknande paginering när man hämtar en sida nära slutet.

I den här självstudien implementeras anpassad sidindelning med nyckelordet ROW_NUMBER(). För mer information om hur du använder tabellvariabeln och SET ROWCOUNT-tekniken, se Effektivt bläddra genom stora mängder data.

Nyckelordet ROW_NUMBER() associerade en rangordning med varje post som returnerades enligt en viss ordning med hjälp av följande syntaxen.

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

ROW_NUMBER() returnerar ett numeriskt värde som anger rangordningen för varje post när det gäller den angivna ordningen. Om du till exempel vill se rangordningen för varje produkt, sorterad från den dyraste till den lägsta, kan vi använda följande fråga:

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

Bild 5 visar den här frågans resultat när den körs genom frågefönstret i Visual Studio. Observera att produkterna sorteras efter pris, tillsammans med en prisrankning för varje rad.

Prisrankningen ingår för varje returnerad post

Bild 5: Prisrankningen ingår för varje returnerad post

Anmärkning

ROW_NUMBER() är bara en av de många nya rankningsfunktionerna i SQL Server 2005. En mer ingående diskussion om ROW_NUMBER(), tillsammans med de andra rankningsfunktionerna, finns i Returning Ranked Results with Microsoft SQL Server 2005 (Returnera rankade resultat med Microsoft SQL Server 2005).

När resultatet rangordnas efter den angivna ORDER BY kolumnen i OVER -satsen (UnitPricei exemplet ovan) måste SQL Server sortera resultatet. Det här är en snabb åtgärd om det finns ett grupperat index över de kolumner som resultaten sorteras efter, eller om det finns ett täckande index, men kan bli dyrare annars. För att förbättra prestandan för tillräckligt stora frågor kan du överväga att lägga till ett icke-grupperat index för kolumnen som resultaten sorteras efter. Mer detaljerad information om prestandaöverväganden finns i Rankningsfunktioner och prestanda i SQL Server 2005 .

Rangordningsinformationen som returneras av ROW_NUMBER() kan inte användas direkt i WHERE -satsen. En härledd tabell kan dock användas för att returnera resultatet ROW_NUMBER() , som sedan kan visas i WHERE -satsen. Följande fråga använder till exempel en härledd tabell för att returnera kolumnerna ProductName och UnitPrice, tillsammans med ROW_NUMBER() resultatet, och använder sedan en WHERE sats för att endast returnera de produkter vars prisrankning är mellan 11 och 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

Om vi utökar det här konceptet lite längre kan vi använda den här metoden för att hämta en specifik sida med data med tanke på önskat startradsindex och värden för maximala rader:

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

Anmärkning

Som vi kommer att se senare i den här självstudien indexeras den StartRowIndex som tillhandahålls av ObjectDataSource från och med noll, medan värdet ROW_NUMBER() som returneras av SQL Server 2005 indexeras från och med 1. WHERE Satsen returnerar därför de poster där PriceRank är strikt större än StartRowIndex och mindre än eller lika medStartRowIndex + MaximumRows .

Nu när vi har diskuterat hur ROW_NUMBER() kan användas för att hämta en viss sida med data givet värdena för startradsindex och maximalt antal rader, måste vi nu implementera den här logiken som metoder i DAL och BLL.

När vi skapar den här frågan måste vi bestämma i vilken ordning resultatet ska rangordnas. låt oss sortera produkterna efter deras namn i alfabetisk ordning. Det innebär att vi med den anpassade pagineringsimplementeringen i denna självstudie inte kommer att kunna skapa en anpassad rapport som både är paginerad och kan sorteras. I nästa handledning får vi dock se hur sådan funktionalitet kan tillhandahållas.

I föregående avsnitt skapade vi DAL-metoden som en ad hoc SQL-instruktion. Tyvärr gillar T-SQL-parsern i Visual Studio som används av TableAdapter-guiden inte den OVER syntax som används av ROW_NUMBER() funktionen. Därför måste vi skapa den här DAL-metoden som en lagrad procedur. Välj Serverutforskaren på menyn Visa (eller tryck på Ctrl+Alt+S) och expandera NORTHWND.MDF noden. Om du vill lägga till en ny lagrad procedur högerklickar du på noden Lagrade procedurer och väljer Lägg till en ny lagrad procedur (se bild 6).

Lägg till en ny lagrad procedur för paginering genom produkterna

Bild 6: Lägg till en ny lagrad procedur för att bläddra genom produkterna

Den här lagrade proceduren bör acceptera två heltalsindataparametrar – @startRowIndex och @maximumRows och använda funktionen ROW_NUMBER(), sortera efter fältet ProductName, och returnera endast de rader som är större än det angivna @startRowIndex och mindre än eller lika med @startRowIndex + @maximumRow s. Ange följande skript i den nya lagrade proceduren och klicka sedan på ikonen Spara för att lägga till den lagrade proceduren i databasen.

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)

När du har skapat den lagrade proceduren tar du en stund att testa den. Högerklicka på namnet på den GetProductsPaged lagrade proceduren i Serverutforskaren och välj alternativet Kör. Visual Studio frågar sedan efter indataparametrarna @startRowIndex och @maximumRow s (se bild 7). Prova olika värden och granska resultatet.

Ange ett värde för klassen <span= @startRowIndex and @maximumRows Parameters" />

Bild 7: Ange ett värde för parametrarna @startRowIndex och @maximumRows

När du har valt dessa indataparametrar visas resultatet i fönstret Utdata. Bild 8 visar resultatet när du skickar in 10 för både parametrarna @startRowIndex och @maximumRows .

De poster som skulle visas på den andra sidan av data returneras

Bild 8: De poster som visas på den andra sidan med data returneras (Klicka om du vill visa en bild i full storlek)

När den här lagrade proceduren har skapats är vi redo att skapa ProductsTableAdapter metoden. Öppna typdatauppsättningen Northwind.xsdProductsTableAdapter, högerklicka i och välj alternativet Lägg till fråga. I stället för att skapa frågan med hjälp av en ad hoc SQL-instruktion skapar du den med hjälp av en befintlig lagrad procedur.

Skapa DAL-metoden med hjälp av en befintlig lagrad procedur

Bild 9: Skapa DAL-metoden med hjälp av en befintlig lagrad procedur

Därefter uppmanas vi att välja den lagrade procedur som ska anropas. Välj den GetProductsPaged lagrade proceduren i listrutan.

Välj den lagrade proceduren GetProductsPaged i listan Drop-Down

Bild 10: Välj den lagrade proceduren GetProductsPaged i Drop-Down-listan

Nästa skärm frågar dig sedan vilken typ av data som returneras av den lagrade proceduren: tabelldata, ett enda värde eller inget värde. Eftersom den GetProductsPaged lagrade proceduren kan returnera flera poster, bör du ange att den returnerar tabelldata.

Ange att den lagrade proceduren returnerar tabelldata

Bild 11: Anger att den lagrade proceduren returnerar tabelldata

Ange slutligen namnen på de metoder som du vill skapa. Precis som med våra tidigare självstudier kan du gå vidare och skapa metoder med hjälp av både Fill a DataTable och Return a DataTable. Namnge den första metoden FillPaged och den andra GetProductsPaged.

Namnge metoderna FillPaged och GetProductsPaged

Bild 12: Namnge metoderna FillPaged och GetProductsPaged

Förutom att skapa en DAL-metod för att returnera en viss sida med produkter måste vi också tillhandahålla sådana funktioner i BLL. Precis som DAL-metoden måste metoden GetProductsPaged för BLL acceptera två heltalsindata för att ange Index för startrad och Maximalt antal rader, och måste bara returnera de poster som ligger inom det angivna intervallet. Skapa en sådan BLL-metod i klassen ProductsBLL som bara anropar ner till metoden GETProductsPaged i DAL, så här:

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

Du kan använda valfritt namn för BLL-metodens indataparametrar, men som vi snart ser väljer du att använda startRowIndex och maximumRows sparar oss från ett extra arbete när du konfigurerar en ObjectDataSource för att använda den här metoden.

Steg 4: Konfigurera ObjectDataSource för att använda anpassad sidindelning

Med BLL- och DAL-metoderna färdiga för att komma åt en viss delmängd av poster, är vi redo att skapa en GridView-kontroll som bläddrar genom dess underliggande poster med hjälp av anpassad sidladdning. Börja med att öppna EfficientPaging.aspx sidan i PagingAndSorting mappen, lägg till en GridView på sidan och konfigurera den så att den använder en ny ObjectDataSource-kontroll. I våra tidigare självstudier har vi ofta konfigurerat ObjectDataSource för att använda ProductsBLL klassens GetProducts metod. Den här gången vill vi dock använda GetProductsPaged metoden i stället, eftersom GetProducts metoden returnerar alla produkter i databasen medan GetProductsPaged returnerar bara en viss delmängd av poster.

Konfigurera ObjectDataSource att använda metoden GetProductsPaged för ProductsBLL-klassen

Bild 13: Konfigurera ObjectDataSource att använda GetProductsPaged-metoden i ProductsBLL-klassen.

Eftersom vi skapar en skrivskyddad GridView bör du ta ett ögonblick att ställa in metoddroplisten i flikarna INSERT, UPDATE och DELETE till (Ingen).

Därefter uppmanar ObjectDataSource-guiden oss att ange källorna till GetProductsPaged metodens värden och startRowIndexmaximumRows indataparametrar. Dessa indataparametrar kommer faktiskt att ställas in av GridView automatiskt, så lämna bara källan till Ingen och klicka på Slutför.

Lämna indataparameterkällorna som Inga

Bild 14: Lämna indataparameterkällorna som Inga

När du har slutfört guiden ObjectDataSource innehåller GridView ett BoundField- eller CheckBoxField-fält för var och en av produktdatafälten. Du kan skräddarsy GridViews utseende så som du vill. Jag har valt att visa endast ProductName, CategoryName, SupplierName, QuantityPerUnit och UnitPrice BoundFields. Konfigurera även GridView för att stödja växling genom att markera kryssrutan Aktivera växling i den smarta taggen. Efter dessa ändringar bör deklarativ markering för GridView och ObjectDataSource se ut ungefär så här:

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

Om du besöker sidan via en webbläsare finns dock inte GridView där.

GridView visas inte

Bild 15: GridView visas inte

GridView saknas eftersom ObjectDataSource för närvarande använder 0 som värden för båda parametrarna GetProductsPagedstartRowIndex och maximumRows indataparametrarna. Därför returnerar den resulterande SQL-frågan inga poster och därför visas inte GridView.

För att åtgärda detta måste vi konfigurera ObjectDataSource för att använda anpassad sidindelning. Detta kan göras i följande steg:

  1. Ange egenskapen ObjectDataSource till EnablePagingtrue detta anger för ObjectDataSource att den måste skickas till de SelectMethod två ytterligare parametrarna: en för att ange Index för startrad (StartRowIndexParameterName) och en för att ange maximalt antal rader (MaximumRowsParameterName).
  2. Ange ObjectDataSource s StartRowIndexParameterName och MaximumRowsParameterName egenskaper på rätt sättStartRowIndexParameterName egenskaperna och MaximumRowsParameterName anger namnen på de indataparametrar som skickas till SelectMethod för anpassade ändamål för sidnumrering. Som standard är startIndexRow dessa parameternamn och maximumRows, vilket är anledningen till att jag använde dessa värden för indataparametrarna när jag skapade GetProductsPaged metoden i BLL. Om du väljer att använda olika parameternamn för BLL-metoden GetProductsPaged , startIndex till exempel och maxRows, måste du till exempel ange ObjectDataSources StartRowIndexParameterName och MaximumRowsParameterName egenskaper i enlighet med detta (till exempel startIndex för StartRowIndexParameterName och maxRows för MaximumRowsParameterName).
  3. Ange egenskapen för ObjectDataSource-objektet till namnet på den metod som returnerar det totala antalet poster som bläddras igenom (SelectCountMethod) och kom ihåg att klassens TotalNumberOfProducts metod returnerar det totala antalet poster genom att använda en DAL-metod som kör en fråga. Den här informationen behövs av ObjectDataSource för att korrekt återge pagineringsgränssnittet.
  4. Ta bort elementen startRowIndex och maximumRows<asp:Parameter> från ObjectDataSources deklarativa markering när du konfigurerar ObjectDataSource via guiden, Visual Studio lade automatiskt till två <asp:Parameter> element för GetProductsPaged metodens indataparametrar. Genom att ange EnablePaging till trueskickas dessa parametrar automatiskt. Om de också visas i den deklarativa syntaxen försöker ObjectDataSource skicka fyra parametrar till GetProductsPaged metoden och två parametrar till TotalNumberOfProducts metoden. Om du glömmer att ta bort dessa <asp:Parameter> element får du ett felmeddelande när du besöker sidan via en webbläsare: ObjectDataSource 'ObjectDataSource1' kunde inte hitta en icke-generisk metod 'TotalNumberOfProducts' som har parametrar: startRowIndex, maximumRows.

När du har gjort dessa ändringar bör ObjectDataSources deklarativa syntax se ut så här:

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

Observera att EnablePaging egenskaperna och SelectCountMethod har angetts och att elementen <asp:Parameter> har tagits bort. Bild 16 visar en skärmbild av fönstret Egenskaper efter att ändringarna har gjorts.

För att använda anpassad sidindelning, konfigurera ObjectDataSource-kontrollen

Bild 16: För att använda anpassad paginering, konfigurera ObjectDataSource-kontrollen

När du har gjort dessa ändringar går du till den här sidan via en webbläsare. Du bör se 10 produkter i listan, ordnade i alfabetisk ordning. Ta en stund att gå igenom data en sida i taget. Även om det inte finns någon synlig skillnad från slutanvändarens perspektiv mellan standardpaginering och anpassad paginering, hanterar anpassad paginering stora datamängder mer effektivt eftersom den bara hämtar de poster som behöver visas för en viss sida.

Den data som är sorterad efter produktens namn pagineras med anpassad pagination

Bild 17: Data, sorterade efter produktens namn, är paginerade med anpassad sidindelning (klicka för att visa bilden i full storlek)

Anmärkning

Med anpassad sidindelning lagras det sidantal som returneras av ObjectDataSources SelectCountMethod i GridViews vytillstånd. Andra GridView-variabler samlingen PageIndex, EditIndex, SelectedIndex, DataKeys och så vidare lagras i kontrolltillstånd, vilket sparas oavsett värdet för GridView-egenskapen EnableViewState . Eftersom PageCount-värdet bevaras mellan postbacks med hjälp av view state, är det absolut nödvändigt att aktivera GridView:s view state när du använder ett pagineringsgränssnitt som innehåller en länk för att ta dig till den sista sidan. (Om pagineringsgränssnittet inte innehåller någon direktlänk till den sista sidan, kan du inaktivera viewstate.)

Om du klickar på länken för den sista sidan orsakar det en postback och talar om för GridView att uppdatera sin PageIndex-egenskap. Om den sista sidlänken klickas tilldelar GridView dess PageIndex egenskap till ett värde som är mindre än dess PageCount egenskap. När visningstillståndet PageCount är inaktiverat går värdet förlorat mellan postbacks och PageIndex tilldelas det maximala heltalsvärdet i stället. Sedan försöker GridView fastställa indexet för startraden genom att multiplicera egenskaperna PageSize och PageCount. Detta resulterar i en OverflowException eftersom produkten överskrider den maximala tillåtna heltalsstorleken.

Implementera anpassad sidindelning och sortering

Vår nuvarande anpassade sidindelningsimplementering kräver att den ordning data pagineras specificeras statiskt när du skapar den GetProductsPaged lagrade proceduren. Du kan dock ha noterat att GridViews smarta tagg innehåller kryssrutan Aktivera sortering utöver alternativet Aktivera sidindelning. Tyvärr, om du lägger till sorteringsstöd i GridView med vår nuvarande anpassade sidindelningsimplementering, kommer endast posterna på den aktuella sidan med data att sorteras. Om du till exempel konfigurerar GridView för att även stödja sidsökning och sedan, när du visar den första sidan med data, sorterar produkterna efter namn i fallande ordning, kommer det att ändra ordningen på produkterna på sida 1. Som bild 18 visar, framhävs Carnarvon Tigers som den första produkten vid sortering i omvänd alfabetisk ordning, vilket ignorerar de 71 andra produkterna som alfabetiskt kommer efter Carnarvon Tigers; endast posterna på den första sidan beaktas i sorteringen.

Endast data som visas på den aktuella sidan sorteras

Bild 18: Endast data som visas på den aktuella sidan sorteras (Klicka om du vill visa en bild i full storlek)

Sortering gäller endast för den aktuella sidan med data eftersom sortering sker efter att data har hämtats från BLL-metoden GetProductsPaged , och den här metoden returnerar endast dessa poster för den specifika sidan. För att implementera sortering korrekt måste vi skicka sorteringsuttrycket till GetProductsPaged metoden så att data kan rangordnas korrekt innan vi returnerar den specifika sidan med data. Vi får se hur vi gör detta i vår nästa handledning.

Implementera anpassad paginering och borttagning

Om du aktiverar borttagningsfunktionalitet i en GridView vars data pagineras med hjälp av anpassade pagineringstekniker kommer du att upptäcka att när du tar bort den sista posten från den sista sidan försvinner GridView i stället för att på lämpligt sätt minska GridView:s PageIndex. Om du vill återskapa den här buggen aktiverar du borttagning för handledningen som vi just har skapat. Gå till den sista sidan (sida 9), där du bör se en enda produkt eftersom vi söker igenom 81 produkter, 10 produkter i taget. Ta bort den här produkten.

När du tar bort den sista produkten bör GridView automatiskt gå till den åttonde sidan, och denna funktionalitet visas med standardpaginering. Med anpassad sidbläddring försvinner dock GridView helt enkelt från skärmen efter att den sista produkten har raderats på den sista sidan. Den exakta anledningen till varför detta inträffar ligger lite utanför omfånget för den här handledningen. Se Ta bort den sista posten på den sista sidan från en GridView med anpassad paginering för detaljer på låg nivå om källan till det här problemet. Sammanfattningsvis beror det på följande stegsekvens som utförs av GridView när knappen Ta bort klickas:

  1. Ta bort posten
  2. Hämta lämpliga poster för visning för angivna PageIndex och PageSize
  3. Kontrollera att PageIndex inte överskrider antalet sidor med data i datakällan. Om det gör det minskar du automatiskt egenskapen GridView PageIndex
  4. Binda lämplig sida med data till GridView med hjälp av posterna som hämtas i steg 2

Problemet uppstår på grund av att i steg 2 används PageIndex när du hämtar poster som ska visas, vilket fortfarande är PageIndex från den sista sidan, vars enda post nyss raderades. I steg 2 returneras därför inga poster eftersom den sista sidan med data inte längre innehåller några poster. Sedan inser GridView i steg 3 att dess PageIndex egenskap är större än det totala antalet sidor i datakällan (eftersom vi har tagit bort den sista posten på den sista sidan) och därför minskar dess PageIndex egenskap. I steg 4 försöker GridView binda sig till de data som hämtas i steg 2. Men i steg 2 returnerades inga poster, vilket resulterade i en tom GridView. Med standardpaginering visas inte det här problemet eftersom alla poster hämtas från datakällan i steg 2.

För att åtgärda detta har vi två alternativ. Den första är att skapa en händelsehanterare för GridViews RowDeleted händelsehanterare som avgör hur många poster som visades på sidan som just har tagits bort. Om det bara fanns en post måste posten som just tagits bort ha varit den sista och vi måste minska GridViews PageIndex. Naturligtvis vill vi bara uppdatera PageIndex om borttagningsåtgärden faktiskt lyckades, vilket kan fastställas genom att se till att e.Exception egenskapen är null.

Den här metoden fungerar eftersom PageIndex uppdateras efter steg 1 men före steg 2. Därför returneras den lämpliga uppsättningen poster i steg 2. Det gör du genom att använda kod som följande:

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

En alternativ lösning är att skapa en händelsehanterare för ObjectDataSource-händelsen RowDeleted och ange AffectedRows egenskapen till värdet 1. När du har raderat posten i steg 1 (men innan du hämtar data igen i steg 2) uppdaterar GridView dess PageIndex egenskap om en eller flera rader påverkades av åtgärden. Egenskapen anges dock AffectedRows inte av ObjectDataSource och därför utelämnas det här steget. Ett sätt att köra det här steget är att manuellt ange AffectedRows egenskapen om borttagningsåtgärden har slutförts. Detta kan åstadkommas med hjälp av kod som följande:

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

Koden för båda dessa händelsehanterare finns i klassen code-behind i EfficientPaging.aspx exemplet.

Jämföra prestanda för standard- och anpassad paginering

Eftersom anpassad växling endast hämtar de poster som behövs, medan standardväxling returnerar alla poster för varje sida som visas, är det tydligt att anpassad växling är effektivare än standardväxling. Men hur mycket mer effektiv är egentligen anpassad sidindelning? Vilka sorters prestandaförbättringar kan man se genom att gå från standardpaginering till anpassad paginering?

Tyvärr finns det ingen lösning som passar alla här. Prestandavinsten beror på ett antal faktorer, varav de mest framträdande två är antalet poster som bläddras igenom och belastningen på databasservern och kommunikationskanalerna mellan webbservern och databasservern. För små tabeller med bara några dussin poster kan prestandaskillnaden vara försumbar. För stora tabeller, med tusentals till hundratusentals rader, är dock prestandaskillnaden akut.

En av mina artiklar, "Anpassad paginering i ASP.NET 2.0 med SQL Server 2005", innehåller några prestandatester som jag körde för att visa skillnaderna i prestanda mellan dessa två pagineringstekniker vid bläddring genom en databastabell med 50 000 poster. I dessa tester undersökte jag både tiden för att köra frågan på SQL Server-nivå (med SQL Profiler) och på sidan ASP.NET med hjälp av ASP.NET spårningsfunktioner. Tänk på att dessa tester kördes i min utvecklingsruta med en enda aktiv användare och därför är ovetenskapliga och inte efterliknar typiska mönster för webbplatsbelastning. Oavsett visar resultaten de relativa skillnaderna i exekveringstid för standard- och anpassad paginering vid arbete med tillräckligt stora mängder data.

Genomsnittlig varaktighet (sek) Läser
Standardinställning för Paging i SQL Profiler 1.411 383
Anpassad paginering för SQL Profiler 0.002 29
Standardpaging för ASP.NET-körning 2.379 Ej tillämpligt
Anpassad paginering och spårning i ASP.NET 0.029 Ej tillämpligt

Såsom du kan se, krävdes i genomsnitt 354 färre läsningar för att hämta en viss sida med data och det slutfördes på en bråkdel av tiden. På ASP.NET-sidan kunde en anpassad sida renderas på nästan 1/100-del av tiden jämfört med när standardväxling användes.

Sammanfattning

Standardbladindelning är lätt att implementera. Markera bara kryssrutan Aktivera bladindelning i smart tagg för datawebbkontrollen, men sådan enkelhet sker på bekostnad av prestanda. Med standardpaginering returneras alla poster när en användare begär en sida med data, även om endast en liten del av dem kan visas. För att bekämpa den här prestandabelastningen erbjuder ObjectDataSource ett alternativt växlingsalternativ för anpassad växling.

Även om anpassad paginering löser prestandaproblem med standardpaginering genom att endast hämta de poster som behöver visas, är det mer komplext att implementera anpassad paginering. Först måste en fråga skrivas som korrekt (och effektivt) kommer åt den specifika delmängden av begärda poster. Detta kan åstadkommas på flera olika sätt. den vi undersökte i den här självstudien är att använda sql Server 2005 s nya ROW_NUMBER() funktion för att rangordna resultat och sedan returnera bara de resultat vars rankning faller inom ett angivet intervall. Dessutom måste vi lägga till ett sätt att fastställa det totala antalet poster som bläddras igenom. När vi har skapat dessa DAL- och BLL-metoder måste vi också konfigurera ObjectDataSource så att den kan avgöra hur många totala poster som bläddras igenom och korrekt kan skicka värdena Start Row Index och Maximum Rows till BLL.

Även om implementering av anpassad paginering kräver ett antal steg och inte alls är lika enkelt som standardpaginering, är anpassad paginering en nödvändighet när du hanterar tillräckligt stora mängder data. Som de undersökta resultaten visade kan anpassad paginering minska återgivningstiden för ASP.NET-sidor med några sekunder och minska belastningen på databasservern med en eller flera storleksordningar.

Lycka till med programmerandet!

Om författaren

Scott Mitchell, författare till sju ASP/ASP.NET-böcker och grundare av 4GuysFromRolla.com, har arbetat med Microsofts webbtekniker sedan 1998. Scott arbetar som oberoende konsult, tränare och författare. Hans senaste bok är Sams Teach Yourself ASP.NET 2.0 på 24 timmar. Han kan nås på mitchell@4GuysFromRolla.com.