大量のデータを効率的にページングする (C#)

作成者: Scott Mitchell

PDF のダウンロード

データ プレゼンテーション コントロールの既定のページング オプションは、データのサブセットのみが表示されている場合でも、基になるデータ ソース コントロールがすべてのレコードを取得するために、大量のデータを操作する場合には適しません。 このような状況では、カスタム ページングに目を向ける必要があります。

はじめに

前のチュートリアルで説明したように、ページングは次の 2 つの方法のいずれかで実装できます。

  • 既定のページング は、データ Web コントロールのスマート タグで [ページングを有効にする] オプションをオンにするだけで実装できます。ただし、データのページを表示するたびに、ObjectDataSource は、そのサブセットのみがページに表示されている場合でも、 すべての レコードを取得します
  • カスタム ページング では、ユーザーが要求したデータの特定のページに対して表示する必要があるレコードのみをデータベースから取得することで、既定のページングのパフォーマンスが向上します。ただし、カスタム ページングには、既定のページングよりも少し多くの労力を実装する必要があります

実装が簡単なため、チェックボックスをチェックするだけで完了です。 既定のページングは魅力的なオプションです。 ただし、すべてのレコードを取得する際の単純なアプローチでは、十分に大量のデータをページングする場合や、多数の同時ユーザーを持つサイトをページングする場合は、意味のない選択になります。 このような状況では、応答性の高いシステムを提供するために、カスタム ページングに目を向ける必要があります。

カスタム ページングの課題は、特定のデータ ページに必要なレコードの正確なセットを返すクエリを記述できることです。 幸いなことに、Microsoft SQL Server 2005 には、結果をランク付けするための新しいキーワード (keyword)が用意されています。これにより、レコードの適切なサブセットを効率的に取得できるクエリを作成できます。 このチュートリアルでは、この新しいSQL Server 2005 キーワード (keyword)を使用して、GridView コントロールにカスタム ページングを実装する方法について説明します。 カスタム ページングのユーザー インターフェイスは既定のページングの場合と同じですが、カスタム ページングを使用して 1 ページから次のページにステップ実行すると、既定のページングよりも数桁速くなる場合があります。

注意

カスタム ページングによって示されるパフォーマンスの正確な向上は、ページングされるレコードの合計数と、データベース サーバーに配置される負荷によって異なります。 このチュートリアルの最後では、カスタム ページングによって得られるパフォーマンスの利点を示す大まかなメトリックについて説明します。

手順 1: カスタム ページング プロセスについて

データをページングする場合、ページに表示される正確なレコードは、要求されるデータのページと、ページごとに表示されるレコードの数によって異なります。 たとえば、81 個の製品をページングし、1 ページあたり 10 個の製品を表示するとします。 最初のページを表示する場合は、製品 1 から 10 が必要です。2 ページ目を見るときは、製品 11 から 20 に興味があります。

取得する必要があるレコードとページング インターフェイスのレンダリング方法を指定する 3 つの変数があります。

  • 開始行インデックス 表示するデータのページ内の最初の行のインデックス。このインデックスは、ページ インデックスにページごとに表示するレコードを乗算し、1 つのレコードを追加することによって計算できます。 たとえば、レコードを一度に 10 個ずつページングする場合、最初のページ (ページ インデックスが 0) の場合、開始行インデックスは 0 * 10 + 1、または 1 になります。2 番目のページ (ページ インデックスが 1) の場合、開始行インデックスは 1 * 10 + 1、または 11 です。
  • [最大行数 ] ページごとに表示するレコードの最大数。 最後のページでは、返されるレコードがページ サイズよりも少なくなる可能性があるため、この変数は最大行と呼ばれます。 たとえば、ページあたり 10 レコードの 81 個の製品をページングする場合、9 番目と最後のページには 1 つのレコードだけが含まれます。 ただし、[最大行数] の値よりも多くのレコードが表示されるページはありません。
  • [Total Record Count]\(総レコード数 \) ページングされるレコードの合計数。 この変数は、特定のページに対して取得するレコードを決定するために必要ではありませんが、ページング インターフェイスを指示します。 たとえば、ページングされる製品が 81 個ある場合、ページング インターフェイスはページング UI に 9 つのページ番号を表示することを認識します。

既定のページングでは、開始行インデックスはページ インデックスの積とページ サイズに 1 を加えた値として計算されますが、[最大行数] は単にページ サイズです。 既定のページングでは、データのページをレンダリングするときにデータベースからすべてのレコードが取得されるため、各行のインデックスがわかっているため、行インデックスの開始行への移動は簡単なタスクになります。 さらに、Total Record Count は DataTable 内のレコードの数 (またはデータベースの結果を保持するために使用されているオブジェクト) の数に過ぎず、簡単に使用できます。

[開始行インデックス] 変数と [最大行数] 変数を指定すると、カスタム ページングの実装では、開始行インデックス以降のレコードの正確なサブセットと、その後の最大行数までのレコードのみを返す必要があります。 カスタム ページングには、次の 2 つの課題があります。

  • 指定した開始行インデックスでレコードの返しを開始できるように、ページングされるデータ全体の各行に行インデックスを効率的に関連付けることができる必要があります
  • ページングされるレコードの合計数を指定する必要があります

次の 2 つの手順では、これら 2 つの課題に対応するために必要な SQL スクリプトを確認します。 SQL スクリプトに加えて、DAL と BLL にメソッドを実装する必要もあります。

手順 2: ページングされるレコードの合計数を返す

表示されるページのレコードの正確なサブセットを取得する方法を調べる前に、最初にページングされるレコードの合計数を返す方法を見てみましょう。 この情報は、ページング ユーザー インターフェイスを適切に構成するために必要です。 特定の SQL クエリによって返されるレコードの合計数は、集計関数をCOUNT使用して取得できます。 たとえば、テーブル内のレコードの合計数を Products 調べるには、次のクエリを使用できます。

SELECT COUNT(*)
FROM Products

この情報を返すメソッドを DAL に追加しましょう。 特に、上記のステートメントを実行する という DAL TotalNumberOfProducts() メソッドを SELECT 作成します。

まず、フォルダー内の Northwind.xsd 型指定された DataSet ファイルを App_Code/DAL 開きます。 次に、Designerで をProductsTableAdapter右クリックし、[クエリの追加] を選択します。 前のチュートリアルで説明したように、これにより、呼び出されたときに特定の SQL ステートメントまたはストアド プロシージャを実行する新しいメソッドを DAL に追加できます。 前のチュートリアルの TableAdapter メソッドと同様に、アドホック SQL ステートメントを使用することを選択します。

アドホック SQL ステートメントを使用する

図 1: アドホック SQL ステートメントを使用する

次の画面では、作成するクエリの種類を指定できます。 このクエリは単一のスカラー値を返すので、テーブル内のレコードの合計数は Products 、singe 値オプションを返す を選択 SELECT します。

1 つの値を返す SELECT ステートメントを使用するようにクエリを構成する

図 2: 1 つの値を返す SELECT ステートメントを使用するようにクエリを構成する

使用するクエリの種類を指定したら、次にクエリを指定する必要があります。

SELECT COUNT(*) FROM Products クエリを使用する

図 3: SELECT COUNT(*) FROM Products クエリを使用する

最後に、 メソッドの名前を指定します。 前述のように、 を使用 TotalNumberOfProductsしましょう。

DAL メソッドに TotalNumberOfProducts という名前を付けます

図 4: DAL メソッドに TotalNumberOfProducts という名前を付けます

[完了] をクリックすると、ウィザードによってメソッドが TotalNumberOfProducts DAL に追加されます。 DAL でメソッドを返すスカラーは、SQL クエリの結果が の場合に null 許容型を返します NULLCOUNTただし、クエリは常に非NULL値を返します。DAL メソッドは null 許容整数を返します。

DAL メソッドに加えて、BLL のメソッドも必要です。 クラス ファイルをProductsBLL開き、DAL TotalNumberOfProducts の メソッドを呼び出すメソッドを追加TotalNumberOfProductsします。

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

DAL s TotalNumberOfProducts メソッドは null 許容整数を返しますが、標準整数を ProductsBLL 返すようにクラス s TotalNumberOfProducts メソッドを作成しました。 したがって、DAL s メソッドによって返される ProductsBLL null 許容整数の値部分をクラス s TotalNumberOfProductsTotalNumberOfProducts メソッドから返す必要があります。 を GetValueOrDefault() 呼び出すと、null 許容整数の値が存在する場合は返されます。ただし、null 許容整数が の場合は null、既定の整数値 0 が返されます。

手順 3: レコードの正確なサブセットを返す

次のタスクは、DAL と BLL で、前に説明した [開始行インデックス] 変数と [最大行数] 変数を受け入れ、適切なレコードを返すメソッドを作成することです。 その前に、まず必要な SQL スクリプトを見てみましょう。 私たちが直面している課題は、先頭行インデックス (およびレコードの最大数まで) から始まるレコードだけを返すことができるように、ページングされる結果全体の各行にインデックスを効率的に割り当てることができる必要があるということです。

行インデックスとして機能する列がデータベース テーブルに既に存在する場合、これは困難ではありません。 最初の製品ProductIDには 1、2 番目の ProductID a 2 などがあるため、テーブルのフィールドで十分であると考Productsえるかもしれません。 ただし、製品を削除するとシーケンスにギャップが残り、このアプローチは無効になります。

行インデックスをページスルーするデータに効率的に関連付けるために使用される一般的な手法は 2 つあります。これにより、レコードの正確なサブセットを取得できます。

  • SQL Server 2005 s ROW_NUMBER() Keyword new to SQL Server 2005 を使用すると、ROW_NUMBER()キーワード (keyword)はいくつかの順序に基づいて、返された各レコードにランク付けを関連付けます。 このランク付けは、各行の行インデックスとして使用できます。

  • テーブル変数の使用とSET ROWCOUNTSQL ServerSET ROWCOUNT ステートメントを使用して、クエリを終了する前に処理する必要があるレコードの合計数を指定できます。table 変数は、一時テーブルと同じ表形式データを保持できるローカル T-SQL 変数です。 このアプローチは、Microsoft SQL Server 2005 と SQL Server 2000 の両方で同様に機能します (一方ROW_NUMBER()、このアプローチは SQL Server 2005 でのみ機能します)。

    ここでの考え方は、データがページングされるテーブルの主キーの列と列を含 IDENTITY むテーブル変数を作成することです。 次に、データがページングされるテーブルの内容がテーブル変数にダンプされ、テーブル内の各レコードに対して (列を介して IDENTITY ) シーケンシャル行インデックスが関連付けられます。 テーブル変数が設定されると、 SELECT 基になるテーブルと結合されたテーブル変数のステートメントを実行して、特定のレコードをプルできます。 ステートメントは SET ROWCOUNT 、テーブル変数にダンプする必要があるレコードの数をインテリジェントに制限するために使用されます。

    この方法の効率性は、 SET ROWCOUNT 要求されるページ番号に基づいています。値には [開始行インデックス] の値と [最大行数] が割り当てられます。 データの最初の数ページなど、番号の小さいページをページングする場合、この方法は非常に効率的です。 ただし、末尾付近のページを取得すると、既定のページングに似たパフォーマンスが示されます。

このチュートリアルでは、キーワード (keyword)を使用してカスタム ページングをROW_NUMBER()実装します。 テーブル変数と SET ROWCOUNT 手法の使用の詳細については、「大量の データを効率的にページングする」を参照してください。

キーワード (keyword)はROW_NUMBER()、次の構文を使用して、特定の順序で返される各レコードにランク付けを関連付けていました。

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

ROW_NUMBER() は、指定された順序に関して各レコードのランクを指定する数値を返します。 たとえば、最もコストの高いものから最も低いものまで順に並べ替えられた各製品のランクを確認するには、次のクエリを使用できます。

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

図 5 は、Visual Studio のクエリ ウィンドウで実行したときのこのクエリの結果を示しています。 各行の価格ランクと共に、製品は価格順に並べ替えられます。

価格ランクは、返された各レコードに含まれます

図 5: 返されるレコードごとに価格ランクが含まれる

注意

ROW_NUMBER()は、SQL Server 2005 で使用できる多くの新しいランク付け関数の 1 つに過ぎません。 の詳細については、他のROW_NUMBER()ランク付け関数と共に、「Microsoft SQL Server 2005 でランク付けされた結果を返す」を参照してください。

句で指定したORDER BYOVERで結果をランク付けする場合 (UnitPrice上記の例では)、SQL Server結果を並べ替える必要があります。 これは、結果が並べ替えられている列に対してクラスター化インデックスがある場合、またはカバーインデックスがある場合は簡単に行えますが、それ以外の場合はコストが高くなります。 十分に大きなクエリのパフォーマンスを向上させるには、結果の順序が指定された列に非クラスター化インデックスを追加することを検討してください。 パフォーマンスに関する考慮事項の詳細については、「SQL Server 2005 の関数とパフォーマンスのランク付け」を参照してください。

によって ROW_NUMBER() 返されるランク付け情報は、 句で WHERE 直接使用することはできません。 ただし、派生テーブルを使用して結果を ROW_NUMBER() 返すことができます。これにより、 句に WHERE 表示されます。 たとえば、次のクエリでは、派生テーブルを使用して ProductName 列と UnitPrice 列を結果と共 ROW_NUMBER() に返し、 句を WHERE 使用して価格ランクが 11 ~ 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

この概念をさらに拡張して、このアプローチを利用して、必要な [開始行インデックス] と [最大行数] の値を指定して、特定のデータ ページを取得できます。

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

注意

このチュートリアルの後半で説明するように、StartRowIndexObjectDataSource によって提供される には 0 から始まるインデックスが付けられますが、ROW_NUMBER()SQL Server 2005 によって返される値は 1 から始まります。 したがって、 句は WHERE 、 が PriceRank 厳密に より StartRowIndex 大きく、 以下のレコードを StartRowIndex + MaximumRows返します。

開始行インデックスと最大行数の値を指定してデータの特定のページを取得する方法 ROW_NUMBER() について説明したので、このロジックを DAL と BLL のメソッドとして実装する必要があります。

このクエリを作成するときは、結果のランク付けの順序を決定する必要があります。では、製品の名前をアルファベット順に並べ替えることができます。 つまり、このチュートリアルのカスタム ページング実装では、並べ替えも可能なカスタム ページ レポートを作成することはできません。 ただし、次のチュートリアルでは、このような機能を提供する方法について説明します。

前のセクションでは、アドホック SQL ステートメントとして DAL メソッドを作成しました。 残念ながら、TableAdapter ウィザードで使用される Visual Studio の T-SQL パーサーでは、 関数で使用される構文は気にROW_NUMBER()OVERりません。 したがって、この DAL メソッドをストアド プロシージャとして作成する必要があります。 [表示] メニューから [サーバー] エクスプローラーを選択し (または Ctrl + Alt + S キーを押します)、ノードをNORTHWND.MDF展開します。 新しいストアド プロシージャを追加するには、[ストアド プロシージャ] ノードを右クリックし、[新しいストアド プロシージャの追加] を選択します (図 6 を参照)。

製品をページングするための新しいストアド プロシージャを追加する

図 6: 製品をページングするための新しいストアド プロシージャを追加する

このストアド プロシージャでは、2 つの整数入力パラメーター @startRowIndex@maximumRowsを受け取り、フィールドで並べ替えられた関数をProductName使用ROW_NUMBER()して、指定された @startRowIndex@startRowIndex + @maximumRow より大きく、s 以下の行のみを返す必要があります。 新しいストアド プロシージャに次のスクリプトを入力し、[保存] アイコンをクリックして、ストアド プロシージャをデータベースに追加します。

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)

ストアド プロシージャを作成した後、しばらくしてテストします。[サーバー] GetProductsPaged エクスプローラーでストアド プロシージャ名を右クリックし、[実行] オプションを選択します。 その後、Visual Studio によって入力パラメーター @startRowIndex@maximumRow の入力が求められます (図 7 を参照)。 さまざまな値を試し、結果を調べます。

<span class=@startRowIndex および @maximumRows Parameters" /> の値を入力します

図 7: パラメーターと @maximumRows パラメーターの値を@startRowIndex入力する

これらの入力パラメーター値を選択すると、[出力] ウィンドウに結果が表示されます。 図 8 は、 パラメーターと @maximumRows パラメーターの両方に 10 を渡したときの結果を@startRowIndex示しています。

データの 2 ページ目に表示されるレコードが返されます

図 8: データの 2 ページ目に表示されるレコードが返されます (フルサイズの画像を表示する場合はクリックします)

このストアド プロシージャを作成したら、 メソッドを作成 ProductsTableAdapter する準備ができました。 型指定された DataSet を Northwind.xsd 開き、 で ProductsTableAdapter右クリックし、 [クエリの追加] オプションを選択します。 アドホック SQL ステートメントを使用してクエリを作成する代わりに、既存のストアド プロシージャを使用してクエリを作成します。

既存のストアド プロシージャを使用して DAL メソッドを作成する

図 9: 既存のストアド プロシージャを使用して DAL メソッドを作成する

次に、呼び出すストアド プロシージャを選択するように求められます。 GetProductsPagedドロップダウン リストからストアド プロシージャを選択します。

Drop-Down リストから GetProductsPaged ストアド プロシージャを選択します

図 10: Drop-Down リストから GetProductsPaged ストアド プロシージャを選択する

次の画面では、ストアド プロシージャによって返されるデータの種類 (表形式データ、1 つの値、値なし) が表示されます。 ストアド プロシージャは複数の GetProductsPaged レコードを返すことができるため、表形式のデータを返していることを示します。

ストアド プロシージャから表形式データが返されることを示す

図 11: ストアド プロシージャから表形式データが返されることを示す

最後に、作成するメソッドの名前を指定します。 前のチュートリアルと同様に、「DataTable を塗りつぶす」と「DataTable を返す」の両方を使用してメソッドを作成します。 最初のメソッドに、2 番目の メソッド FillPaged に という名前を付けます GetProductsPaged

メソッドに FillPaged および GetProductsPaged という名前を付けます

図 12: メソッドに FillPaged と GetProductsPaged という名前を付ける

製品の特定のページを返す DAL メソッドを作成するだけでなく、BLL でこのような機能を提供する必要もあります。 DAL メソッドと同様に、BLL s GetProductsPaged メソッドは、開始行インデックスと最大行数を指定するために 2 つの整数入力を受け取る必要があり、指定された範囲内にあるレコードのみを返す必要があります。 このような BLL メソッドを ProductsBLL クラスに作成します。このメソッドは、DAL の GetProductsPaged メソッドを呼び出すだけで、次のようになります。

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

BLL メソッドの入力パラメーターには任意の名前を使用できますが、このメソッドを使用するように ObjectDataSource を構成するときに、 を使用startRowIndexmaximumRowsすることを選択すると、追加の作業から保存されます。

手順 4: カスタム ページングを使用するように ObjectDataSource を構成する

レコードの特定のサブセットにアクセスするための BLL メソッドと DAL メソッドが完了したら、カスタム ページングを使用して基になるレコードを通じてページを作成する GridView コントロールを作成する準備ができました。 まず、フォルダー内のページをEfficientPaging.aspxPagingAndSorting開き、ページに GridView を追加し、新しい ObjectDataSource コントロールを使用するように構成します。 以前のチュートリアルでは、多くの場合、クラス s GetProducts メソッドを使用するように ObjectDataSource をProductsBLL構成していました。 ただし、今回は、 メソッドはデータベース内のすべての製品をGetProductsPaged返すのに対GetProductsPagedし、レコードの特定のサブセットのみを返すGetProductsので、代わりに メソッドを使用します。

ProductsBLL クラスの GetProductsPaged メソッドを使用するように ObjectDataSource を構成する

図 13: ProductsBLL クラス s GetProductsPaged メソッドを使用するように ObjectDataSource を構成する

読み取り専用の GridView を作成しているため、INSERT、UPDATE、DELETE タブのメソッド ドロップダウン リストを (None) に設定します。

次に、ObjectDataSource ウィザードによって、メソッドのstartRowIndexソースとmaximumRows入力パラメーター値の入力がGetProductsPaged求められます。 これらの入力パラメーターは実際には GridView によって自動的に設定されるため、ソースを [なし] に設定したまま、[完了] をクリックするだけです。

[入力パラメーター ソース] は [なし] のままにします

図 14: 入力パラメーター ソースは None のままにする

ObjectDataSource ウィザードが完了すると、GridView には各製品データ フィールドの BoundField または CheckBoxField が含まれます。 必要に応じて、GridView の外観を自由に調整できます。 、、、および BoundFields のみをProductNameQuantityPerUnitCategoryNameSupplierName表示することを選択しました。UnitPrice また、スマート タグの [ページングを有効にする] チェック ボックスをオンにして、ページングをサポートするように GridView を構成します。 これらの変更の後、GridView と ObjectDataSource 宣言型マークアップは次のようになります。

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

ただし、ブラウザーを使用してページにアクセスした場合、GridView はどこにも見つかりません。

GridView が表示されない

図 15: GridView が表示されない

ObjectDataSource が現在、 と maximumRows の両方の入力パラメーターの値として 0 を使用しているため、GridView がありません。GetProductsPagedstartRowIndex そのため、結果の SQL クエリはレコードを返さないため、GridView は表示されません。

これを解決するには、カスタム ページングを使用するように ObjectDataSource を構成する必要があります。 これは、次の手順で実行できます。

  1. ObjectDataSource の EnablePaging プロパティを にtrue設定すると、2 つの追加パラメーターに渡すSelectMethod必要があることを ObjectDataSource に示します。1 つは開始行インデックス () を指定し、もう 1 つは最大行数 (StartRowIndexParameterNameMaximumRowsParameterName) を指定します。
  2. ObjectDataSource s StartRowIndexParameterNameMaximumRowsParameterName Properties を設定します。それに応じてStartRowIndexParameterName プロパティと MaximumRowsParameterName プロパティは、カスタム ページングのために に SelectMethod 渡される入力パラメーターの名前を示します。 既定では、これらのパラメーター名は startIndexRowmaximumRowsであるため、BLL で メソッドを GetProductsPaged 作成するときに、これらの値を入力パラメーターに使用しました。 や などの maxRowsstartIndex BLL s GetProductsPaged メソッドに異なるパラメーター名を使用する場合は、それに応じて ObjectDataSource と StartRowIndexParameterNameMaximumRowsParameterName プロパティ (startIndex for StartRowIndexParameterName や maxRows など) を設定するMaximumRowsParameterName必要があります。
  3. ObjectDataSource s SelectCountMethod プロパティを、Paged Through () でページングされるレコードの合計数をTotalNumberOfProducts返すメソッドの名前に設定します。このメソッドは、クエリTotalNumberOfProductsを実行SELECT COUNT(*) FROM Productsする DAL メソッドを使用してページングされるレコードの合計数を返します。ProductsBLL この情報は、ページング インターフェイスを正しくレンダリングするために ObjectDataSource によって必要です。
  4. ウィザードを使用して ObjectDataSource を構成するときに、ObjectDataSource の宣言型マークアップから 要素と maximumRows<asp:Parameter> 要素を削除すると、Visual Studio によってメソッドの入力パラメーターに 2 つの要素が自動的に追加されました。startRowIndexGetProductsPaged<asp:Parameter> を にtrue設定EnablePagingすると、これらのパラメーターは自動的に渡されます。宣言構文にも含まれる場合、ObjectDataSource は 4 つのパラメーターを メソッドにGetProductsPaged渡し、2 つのパラメーターを TotalNumberOfProducts メソッドに渡そうとします。 これらの <asp:Parameter> 要素を削除し忘れた場合、ブラウザーからページにアクセスすると、次のようなエラー メッセージが表示されます。 ObjectDataSource 'ObjectDataSource1' は、startRowIndex、maximumRows というパラメーターを持つ非ジェネリック メソッド 'TotalNumberOfProducts' を見つけることができませんでした

これらの変更を行った後、ObjectDataSource の宣言構文は次のようになります。

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

EnablePagingプロパティと SelectCountMethod プロパティが設定され、要素が削除されていることに<asp:Parameter>注意してください。 図 16 は、これらの変更が行われた後のプロパティ ウィンドウのスクリーン ショットを示しています。

カスタム ページングを使用するには、ObjectDataSource コントロールを構成します

図 16: カスタム ページングを使用するには、ObjectDataSource コントロールを構成する

これらの変更を行った後、ブラウザーからこのページにアクセスします。 アルファベット順に並べ替えられた 10 個の製品が表示されます。 少し時間を取って、一度に 1 ページずつデータをステップ実行します。 既定のページングとカスタム ページングの間にエンド ユーザーの観点から見た視覚的な違いはありませんが、特定のページに対して表示する必要があるレコードのみが取得されるため、大量のデータを使用してページを効率的にページングします。

製品名で並べ替えられたデータは、カスタム ページングを使用してページングされます

図 17: Product s Name で並べ替えられたデータは、カスタム ページングを使用してページングされます (フルサイズの画像を表示する 場合はクリックします)

注意

カスタム ページングでは、ObjectDataSource によって返されるページ数の SelectCountMethod 値が GridView のビューステートに格納されます。 他の PageIndexEditIndexGridView 変数 、、SelectedIndexDataKeys、コレクションなどがコントロール状態で格納されます。これは、GridView EnableViewState の プロパティの値に関係なく保持されます。 値はビュー ステートを PageCount 使用してポストバック間で保持されるため、最後のページに移動するリンクを含むページング インターフェイスを使用する場合は、GridView のビューステートを有効にする必要があります。 (ページング インターフェイスに最後のページへの直接リンクが含まれていない場合は、ビューステートを無効にすることができます)。

最後のページ リンクをクリックするとポストバックが発生し、GridView にそのプロパティを更新 PageIndex するように指示します。 最後のページ リンクをクリックすると、GridView によってプロパティがプロパティより PageCount 1 小さい値に割り当てられますPageIndex。 ビュー ステートが無効になっている場合、 PageCount 値はポストバック間で失われ PageIndex 、 には最大整数値が代わりに割り当てられます。 次に、GridView は、 プロパティと PageCount プロパティを乗算して開始行インデックスのPageSize決定を試みます。 これにより、 OverflowException 製品が許容される最大整数サイズを超えたため、 が発生します。

カスタム ページングと並べ替えを実装する

現在のカスタム ページング実装では、ストアド プロシージャの作成時に、データのページング順序を静的に指定する GetProductsPaged 必要があります。 ただし、GridView のスマート タグには、[ページングを有効にする] オプションに加えて [並べ替えを有効にする] チェック ボックスが含まれていることにご確認ください。 残念ながら、現在のカスタム ページング実装を使用して GridView に並べ替えのサポートを追加すると、現在表示されているデータ ページのレコードのみが並べ替えられます。 たとえば、GridView もページングをサポートするように構成した後、データの最初のページを表示するときに製品名で降順に並べ替えると、1 ページ目の製品の順序が逆になります。 図 18 に示すように、 は、カーナーボン・タイガースの後に来る他の 71 製品をアルファベット順に無視する、逆アルファベット順に並べ替えるときの最初の製品として Carnarvon Tigers を示しています。並べ替えでは、最初のページのレコードのみが考慮されます。

現在のページに表示されているデータのみが並べ替えられます

図 18: 現在のページに表示されているデータのみが並べ替えられます (フルサイズの画像を表示する場合はクリックします)

並べ替えは、データが BLL s GetProductsPaged メソッドから取得された後に並べ替えが行われ、このメソッドは特定のページのレコードのみを返すので、データの現在のページにのみ適用されます。 並べ替えを正しく実装するには、データの特定のページを返す前に GetProductsPaged データを適切にランク付けできるように、並べ替え式を メソッドに渡す必要があります。 これを行う方法については、次のチュートリアルで説明します。

カスタム ページングと削除の実装

カスタム ページング手法を使用してデータがページングされている GridView で機能の削除を有効にすると、最後のページから最後のレコードを削除すると、GridView が適切に減らされるのではなく、GridView PageIndexが消えることがわかります。 このバグを再現するには、先ほど作成したチュートリアルの削除を有効にします。 最後のページ (9 ページ) に移動します。81 個の製品、10 個の製品を一度にページングしているため、1 つの製品が表示されます。 この製品を削除します。

最後の製品を削除すると、GridView 自動的に 8 ページ目に移動し、このような機能は既定のページングで表示されます。 ただし、カスタム ページングでは、最後のページでその最後の製品を削除すると、GridView は画面から完全に消えます。 これが起こる正確な理由は、このチュートリアルの範囲を少し超えています。この問題の原因に関する低レベルの詳細については、「カスタム ページングを使用して GridView から最後のページの最後のレコードを削除する」を参照してください。 要約すると、[削除] ボタンがクリックされたときに GridView によって実行される次の一連の手順が原因です。

  1. レコードを削除する
  2. 指定した PageIndex と に表示する適切なレコードを取得します。 PageSize
  3. がデータ ソース内のデータのページ数を超えていないことを PageIndex 確認します。存在する場合は、GridView の PageIndex プロパティを自動的にデクリメントします。
  4. 手順 2 で取得したレコードを使用して、データの適切なページを GridView にバインドする

この問題は、手順 2 PageIndex では、表示するレコードをつかむときに使用される が PageIndex 、レコードが削除されたばかりの最後のページの であるという事実に起因します。 したがって、手順 2 では、データの最後のページにレコードが含まれていないため、レコードは返 されません 。 次に、手順 3 では、GridView は、その PageIndex プロパティがデータ ソース内のページの合計数より大きい (最後のページの最後のレコードを削除したため) ことを認識し、そのプロパティを PageIndex デクリメントします。 手順 4 では、GridView は手順 2 で取得したデータに自身をバインドしようとします。ただし、手順 2 ではレコードが返されないため、空の GridView になります。 既定のページングでは、手順 2 では すべての レコードがデータ ソースから取得されるため、この問題は発生しません。

これを修正するには、2 つのオプションがあります。 1 つ目は、GridView のイベント ハンドラーの RowDeleted イベント ハンドラーを作成して、削除したばかりのページに表示されたレコードの数を決定することです。 レコードが 1 つしかない場合は、削除したレコードが最後のレコードである必要があり、GridView の PageIndexをデクリメントする必要があります。 もちろん、削除操作が実際に成功した場合にのみ をPageIndex更新します。これは、 プロパティnullが であることをe.Exception確認することで判断できます。

この方法は、手順 1 より後、手順 2 より前の を PageIndex 更新するため機能します。 したがって、手順 2 では、適切なレコードのセットが返されます。 これを実現するには、次のようなコードを使用します。

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

別の回避策として、ObjectDataSource の RowDeleted イベントのイベント ハンドラーを作成し、 プロパティを AffectedRows 値 1 に設定します。 手順 1 でレコードを削除した後 (ただし、手順 2 でデータを再取得する前)、1 つ以上の行が操作の影響を受けた場合、GridView はそのプロパティを更新 PageIndex します。 ただし、 AffectedRows プロパティは ObjectDataSource によって設定されないため、この手順は省略されます。 この手順を実行する方法の 1 つは、削除操作が正常に完了した場合にプロパティを手動で設定 AffectedRows することです。 これは、次のようなコードを使用して実現できます。

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

これらの両方のイベント ハンドラーのコードは、この例の分離コード クラスにあります EfficientPaging.aspx

既定のページングとカスタム ページングのパフォーマンスの比較

カスタム ページングでは必要なレコードのみが取得されますが、既定のページングでは表示されるページごとに すべての レコードが返されるため、カスタム ページングの方が既定のページングよりも効率的であることは明らかです。 しかし、カスタム ページングの効率はどのくらい高いでしょうか。 既定のページングからカスタム ページングに移行すると、どのようなパフォーマンス向上が見られますか?

残念ながら、ここにはすべての答えに適合するサイズはありません。 パフォーマンスの向上は、さまざまな要因によって異なります。最も顕著な 2 つの要因は、ページングされるレコードの数と、データベース サーバーと Web サーバーとデータベース サーバー間の通信チャネルに対する負荷です。 わずか数十件のレコードを含む小さなテーブルの場合、パフォーマンスの違いはごくわずかです。 ただし、数千から数十万行の大規模なテーブルの場合、パフォーマンスの違いは深刻です。

私の記事「ASP.NET 2.0 と SQL Server 2005 のカスタム ページング」には、50,000 レコードのデータベース テーブルをページングするときに、これら 2 つのページング手法のパフォーマンスの違いを示すために実行したパフォーマンス テストがいくつか含まれています。 これらのテストでは、(SQL Profiler を使用して) SQL Server レベルでクエリを実行する時間と、ASP.NET のトレース機能を使用して ASP.NET ページでクエリを実行する時間の両方を調べました。 これらのテストは、アクティブなユーザーが 1 人の開発ボックスで実行されたため、非科学的であり、一般的な Web サイトの読み込みパターンを模倣しないことに注意してください。 いずれの場合も、十分に大量のデータを操作する場合の既定のページングとカスタム ページングの実行時間の相対的な違いを示しています。

平均継続時間 (秒) Reads
既定のページング SQL プロファイラー 1.411 383
カスタム ページング SQL プロファイラー 0.002 29
既定のページング ASP.NET トレース 2.379 N/A
カスタム ページング ASP.NET トレース 0.029 N/A

ご覧のように、データの特定のページを取得する際に必要な読み取りは平均で 354 回少なく、一部の時間で完了しました。 ASP.NET ページでは、既定のページングを使用するときにかかった時間の 1/100 近いカスタム ページをレンダリングできました。

まとめ

既定のページングは、データ Web コントロールのスマート タグの [ページングを有効にする] チェック ボックスチェックを実装するだけで簡単ですが、このようなシンプルさはパフォーマンスを犠牲にします。 既定のページングでは、ユーザーがデータの任意のページを要求すると、そのごく一部しか表示できない場合でも、 すべての レコードが返されます。 このパフォーマンスのオーバーヘッドに対処するために、ObjectDataSource には代替ページング オプションのカスタム ページングが用意されています。

カスタム ページングでは、表示する必要があるレコードのみを取得することで、既定のページングのパフォーマンスの問題が改善されますが、カスタム ページングを実装する方が複雑になります。 最初に、要求されたレコードの特定のサブセットに正しく (かつ効率的に) アクセスするクエリを記述する必要があります。 これは、さまざまな方法で実現できます。このチュートリアルで調べたのは、SQL Server 2005 の新しいROW_NUMBER()関数を使用して結果をランク付けし、そのランクが指定された範囲内にある結果のみを返す方法です。 さらに、ページングされるレコードの合計数を決定する手段を追加する必要があります。 また、これらの DAL メソッドと BLL メソッドを作成した後、ページングされる合計レコード数を決定し、開始行インデックスと最大行数の値を BLL に正しく渡すことができるように ObjectDataSource を構成する必要があります。

カスタム ページングの実装には多くの手順が必要であり、既定のページングほど簡単ではありませんが、十分に大量のデータをページングする場合は、カスタム ページングが必要です。 調査した結果が示したように、カスタム ページングでは、ASP.NET ページのレンダリング時間から数秒が短縮され、データベース サーバーの負荷が 1 桁以上軽くなる可能性があります。

プログラミングに満足!

著者について

7 冊の ASP/ASP.NET 書籍の著者であり、 4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジと協力しています。 Scott は独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・自分自身 ASP.NET 24時間で2.0です。 にアクセスmitchell@4GuysFromRolla.comすることも、ブログを介して アクセスすることもできます。これは でhttp://ScottOnWriting.NET確認できます。