カスタム ページングを適用したデータを並べ替える (C#)

作成者: Scott Mitchell

PDF のダウンロード

前のチュートリアルでは、Web ページにデータを表示するときにカスタム ページングを実装する方法を学習しました。 このチュートリアルでは、前の例を拡張して、カスタム ページングの並べ替えをサポートする方法について説明します。

はじめに

既定のページングと比較して、カスタム ページングでは、データ経由のページングのパフォーマンスを数桁向上させることができます。これにより、大量のデータをページングするときに、カスタム ページングが事実上のページング実装の選択肢になります。 ただし、特にミックスに並べ替えを追加する場合は、カスタム ページングの実装は既定のページングの実装より複雑になります。 このチュートリアルでは、前のチュートリアルの例を拡張して、 を並べ替える カスタム ページングをサポートします。

Note

このチュートリアルは前のチュートリアルに基づいて構築されているため、開始する前に、前のチュートリアルの Web ページ (EfficientPaging.aspx) から <asp:Content> 要素内の宣言構文をコピーし、SortParameter.aspx ページ内の <asp:Content> 要素の間に貼り付けます。 ある ASP.NET ページの機能を別のページにレプリケートする方法の詳細については、チュートリアル「編集および挿入インターフェイスに検証コントロールを追加する」の手順 1 を参照してください。

手順 1: カスタム ページング手法を再確認する

カスタム ページングを適切に機能させるには、開始行インデックスと最大行数パラメーターを指定して、レコードの特定のサブセットを効率的に取得できる手法を実装する必要があります。 この目的を達成するために利用できる手法がいくつかあります。 前のチュートリアルでは、Microsoft SQL Server 2005 の新しい ROW_NUMBER() ランク付け関数を使用してこれを実現する方法について説明しました。 つまり、ROW_NUMBER() ランク付け関数は、指定された並べ替え順序でランク付けされたクエリが返す各行に行番号を割り当てます。 レコードの適切なサブセットは、番号付けされた結果の特定のセクションを返すことによって取得されます。 次のクエリは、ProductName でアルファベット順に並べた結果をランク付けする場合に、11 から 20 までの番号が付いた製品を返すためにこの手法を使用する方法を示しています。

SELECT ProductID, ProductName, ...
FROM
   (SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER
        (ORDER BY ProductName) AS RowRank
    FROM Products) AS ProductsWithRowNumbers
WHERE RowRank > 10 AND RowRank <= 20

この手法は、特定の並べ替え順 (この場合は ProductName をアルファベット順に並べ替えたもの) を使用したページングに適していますが、別の並べ替え式で並べ替えられた結果を表示するにはクエリを変更する必要があります。 理想的には、上記のクエリは OVER 句でパラメーターを使用するように書き換えることができます。

SELECT ProductID, ProductName, ...
FROM
   (SELECT ProductID, ProductName, ..., ROW_NUMBER() OVER
        (ORDER BY @sortExpression) AS RowRank
    FROM Products) AS ProductsWithRowNumbers
WHERE RowRank > 10 AND RowRank <= 20

残念ながら、パラメーター化された ORDER BY 句は使用できません。 代わりに、@sortExpression 入力パラメーターを受け取るストアド プロシージャを作成する必要がありますが、次のいずれかの回避策を使用します。

  • 使用できる並べ替え式ごとにハードコーディングされたクエリを記述します。次に、IF/ELSE T-SQL ステートメントを使用して、実行するクエリを決定します。
  • CASE ステートメントを使用して、@sortExpressio n 個の入力パラメーターに基づいて動的な ORDER BY 式を提供します。詳細については、T-SQL CASE ステートメントのクエリ結果の動的な並べ替えへの使用に関するページを参照してください。
  • 適切なクエリをストアド プロシージャ内の文字列として作成し、sp_executesql 個のシステム ストアド プロシージャを使用して動的クエリを実行します。

これらの回避策にはそれぞれ、いくつかの欠点があります。 最初のオプションは可能性のある並べ替え式ごとにクエリを作成する必要があるため、他の 2 つのオプションほど保守しやすいものではありません。 したがって、後で新しい並べ替え可能なフィールドを GridView に追加する場合は、ストアド プロシージャに戻って更新する必要もあります。 2 番目のアプローチには、文字列以外のデータベース列で並べ替えた場合にパフォーマンスの問題が発生し、1 つ目と同じ保守しやすさの問題が発生する微妙な点があります。 また、3 番目の選択肢は動的 SQL を使用するもので、攻撃者が選択した入力パラメーター値を渡してストアド プロシージャを実行できる場合、SQL インジェクション攻撃のリスクが生じます。

これらのアプローチはどれも完璧ではありませんが、3 つの中で 3 番目の選択肢を最もお勧めできます。 動的 SQL を使用することで、他の 2 つにはないレベルの柔軟性が提供されます。 さらに、SQL インジェクション攻撃は、攻撃者が選択した入力パラメーターを渡してストアド プロシージャを実行できる場合にのみ悪用できます。 DAL はパラメーター化されたクエリを使用するため、ADO.NET はアーキテクチャ経由でデータベースに送信されるパラメーターを保護します。つまり、SQL インジェクション攻撃の脆弱性は、攻撃者がストアド プロシージャを直接実行できる場合にのみ存在します。

この機能を実装するには、Northwind データベースに GetProductsPagedAndSorted という名前の新しいストアド プロシージャを作成します。 このストアド プロシージャは、次の 3 つの入力パラメーターを受け入れる必要があります: @sortExpression (nvarchar(100 型の入力パラメーター) は、結果を並べ替える方法を指定し、OVER 句の ORDER BY テキストの直後に挿入されます。@startRowIndex@maximumRows は、前のチュートリアルで確認した GetProductsPaged ストアド プロシージャからの同じ 2 つの整数入力パラメーターです。 次のスクリプトを使用して、GetProductsPagedAndSorted ストアド プロシージャを作成します。

CREATE PROCEDURE dbo.GetProductsPagedAndSorted
(
    @sortExpression nvarchar(100),
    @startRowIndex int,
    @maximumRows int
)
AS
-- Make sure a @sortExpression is specified
IF LEN(@sortExpression) = 0
    SET @sortExpression = 'ProductID'
-- Issue query
DECLARE @sql nvarchar(4000)
SET @sql = 'SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit,
            UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
            CategoryName, SupplierName
            FROM (SELECT ProductID, ProductName, p.SupplierID, p.CategoryID,
                    QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
                    ReorderLevel, Discontinued,
                  c.CategoryName, s.CompanyName AS SupplierName,
                   ROW_NUMBER() OVER (ORDER BY ' + @sortExpression + ') AS RowRank
            FROM Products AS p
                    INNER JOIN Categories AS c ON
                        c.CategoryID = p.CategoryID
                    INNER JOIN Suppliers AS s ON
                        s.SupplierID = p.SupplierID) AS ProductsWithRowNumbers
            WHERE     RowRank > ' + CONVERT(nvarchar(10), @startRowIndex) +
                ' AND RowRank <= (' + CONVERT(nvarchar(10), @startRowIndex) + ' + '
                + CONVERT(nvarchar(10), @maximumRows) + ')'
-- Execute the SQL query
EXEC sp_executesql @sql

ストアド プロシージャは、@sortExpression パラメーターの値が指定されていることを確認することから始まります。 見つからない場合、結果は ProductID の順にランク付けされます。 次に、動的 SQL クエリが構築されます。 ここでの動的 SQL クエリは、[製品] テーブルからすべての行を取得するために使用した以前のクエリとは若干異なることに注意してください。 前の例では、サブクエリを使用して、各製品に関連するカテゴリ名とサプライヤー名を取得しました。 TableAdapter では、このようなクエリに関連する挿入、更新、および削除メソッドを自動的に作成できないため、この決定は、データ アクセス層の作成に関するチュートリアルに戻り、JOIN を使用する代わりに行われました。 ただし、GetProductsPagedAndSorted ストアド プロシージャでは、カテゴリ名またはサプライヤー名で並べ替える結果に JOIN を使用する必要があります。

この動的クエリは、静的クエリ部分と、@sortExpression@startRowIndex、および @maximumRows パラメーターを連結することによって構築されます。 @startRowIndex@maximumRows は整数パラメーターであるため、正しく連結するには、nvarchars に変換する必要があります。 この動的 SQL クエリが構築されると、sp_executesql を介して実行されます。

@sortExpression@startRowIndex、および @maximumRows パラメーターの値を変えて、このストアド プロシージャをテストしてみましょう。 サーバー エクスプローラーから、ストアド プロシージャ名を右クリックし、[実行] を選択します。 これにより、[ストアド プロシージャの実行] ダイアログ ボックスが表示され、入力パラメーターを入力できます (図 1 参照)。 カテゴリ名で結果を並べ替えるには、@sortExpression パラメーター値に CategoryName を使用します。サプライヤーの会社名で並べ替えるには、CompanyName を使用します。 必要なパラメーター値を入力したら [OK] をクリックします。 結果が出力ウィンドウに表示されます。 図 2 は、UnitPrice を降順に並べ替えたときに 11 から 20 にランク付けされた製品を返す場合の結果を示しています。

Try Different Values for the Stored Procedure s Three Input Parameters

図 1: さまざまな値を試すストアド プロシージャの 3 つの入力パラメーター

The Stored Procedure s Results are Shown in the Output Window

図 2: 出力ウィンドウに表示されたストアド プロシージャの結果 (クリックするとフルサイズの画像が表示されます)

Note

OVER 句で指定した ORDER BY 列で結果をランク付けする場合、SQL Server は結果を並べ替える必要があります。 この操作は、結果が並べ替えられている列にクラスター化インデックスがある場合、またはカバリング インデックスがある場合は短時間でできますが、それ以外の場合はコストが高くなります。 十分に大きなクエリのパフォーマンスを向上させるには、結果を並べ替える列に非クラスター化インデックスを追加することを検討してください。 詳細については、SQL Server 2005 のランク付け関数とパフォーマンスに関するページを参照してください。

手順 2: データ アクセス層とビジネス ロジック層の拡張

GetProductsPagedAndSorted ストアド プロシージャが作成されたので、次の手順では、アプリケーション アーキテクチャを通じてそのストアド プロシージャを実行する手段を提供します。 これには、DAL と BLL の両方に適切なメソッドを追加する必要があります。 まずは DAL にメソッドを追加しましょう。 Northwind.xsd 型指定されたデータセットを開き、ProductsTableAdapter を右クリックし、コンテキスト メニューから [クエリの追加] オプションを選択します。 前のチュートリアルで行ったように、この場合は既存のストアド プロシージャ (GetProductsPagedAndSorted) を使用するように、この新しい DAL メソッドを構成します。 まず、新しい TableAdapter メソッドで既存のストアド プロシージャを使用することを示します。

Choose to Use an Existing Stored Procedure

図 3: 既存のストアド プロシージャの使用の選択

使用するストアド プロシージャを指定するには、次の画面のドロップダウン リストから GetProductsPagedAndSorted ストアド プロシージャを選択します。

Use the GetProductsPagedAndSorted Stored Procedure

図 4: GetProductsPagedAndSorted ストアド プロシージャの使用

このストアド プロシージャは、その結果としてレコードのセットを返します。次の画面は、表形式のデータを返すことを示しています。

Indicate that the Stored Procedure Returns Tabular Data

図 5: 表形式データを返すことを示すストアド プロシージャ

最後に、[DataTable にデータを格納する] パターンと [DataTable を返す] パターンの両方を使用する DAL メソッドを作成し、メソッドにそれぞれFillPagedAndSortedGetProductsPagedAndSorted という名前を付けます。

Choose the Methods Names

図 6: メソッド名の選択

DAL を拡張したので、BLL に切り替える準備ができました。 ProductsBLL クラス ファイルを開き、新しいメソッド (GetProductsPagedAndSorted) を追加します。 このメソッドは、3 つの入力パラメーター (sortExpressionstartRowIndexmaximumRows) を受け入れる必要があり、次に示すように、単純に DAL の GetProductsPagedAndSorted メソッドを呼び出す必要があります。

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

手順 3: SortExpression パラメーターを渡すための ObjectDataSource の構成

GetProductsPagedAndSorted ストアド プロシージャを利用するメソッドを含めるように DAL と BLL 拡張したので、あとは新しい BLL メソッドを使用するために SortParameter.aspx ページで ObjectDataSource を構成し、ユーザーが結果の並べ替えを要求した列に基づいて SortExpression パラメーターを渡すだけです。

まず、ObjectDataSource の SelectMethodGetProductsPaged から GetProductsPagedAndSorted に変更します。 これは、データソースの構成ウィザード、プロパティ ウィンドウ、または宣言構文を通じて直接実行できます。 次に、ObjectDataSource の SortParameterName プロパティ に値を指定する必要があります。 このプロパティが設定されている場合、ObjectDataSource は GridView の SortExpression プロパティを SelectMethod に渡そうとします。 特に、ObjectDataSource は、SortParameterName プロパティの値と同じ名前の入力パラメーターを検索します。 BLL の GetProductsPagedAndSorted メソッドには sortExpression という並べ替え式の入力パラメーターがあるので、ObjectDataSource の SortExpression プロパティを sortExpression に設定します。

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

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

Note

前のチュートリアルと同様に、ObjectDataSource の SelectParameters コレクションに sortExpression、startRowIndex、または maximumRows 入力パラメーターが含まれてないことを確認します。

GridView で並べ替えを有効にするには、GridView のスマート タグの [並べ替えを有効にする] チェックボックスをオンにするだけで、GridView の AllowSorting プロパティが true に設定され、各列のヘッダー テキストが LinkButton としてレンダリングされます。 エンド ユーザーがいずれかのヘッダーの LinkButtons をクリックすると、ポストバックが発生し、次の手順が実行されます。

  1. GridView は、そのSortExpression プロパティをヘッダー リンクがクリックされたフィールドの SortExpression の値に更新します。
  2. ObjectDataSource は BLL の GetProductsPagedAndSorted メソッドを呼び出し、GridView の SortExpression プロパティをメソッドの sortExpression 入力パラメーターの値として渡します (適切な startRowIndex および maximumRows 入力パラメーター値も一緒に渡します)。
  3. BLL は DAL の GetProductsPagedAndSorted メソッドを呼び出します
  4. DAL は GetProductsPagedAndSorted ストアド プロシージャを実行し、@sortExpression パラメーターを渡します (@startRowIndex および @maximumRows 入力パラメーター値も一緒に渡します)。
  5. ストアド プロシージャは、データの適切なサブセットを BLL に返し、BLL は、それを ObjectDataSource に返します。このデータは GridView にバインドされ、HTML にレンダリングされ、エンド ユーザーに送信されます。

図 7 は、UnitPrice が昇順で並べ替えられた場合の結果の最初のページを示しています。

The Results are Sorted by the UnitPrice

図 7: UnitPrice で並べ替えられた結果 (クリックするとフルサイズの画像が表示されます)

現在の実装では、結果を製品名、カテゴリ名、ユニットあたりの数量、単価で正しく並べ替えることができますが、サプライヤー名で結果を並べ替えようとすると、ランタイム例外が発生します (図 8 参照)。

Attempting to Sort the Results by the Supplier Results in the Following Runtime Exception

図 8: サプライヤーの結果ごとに結果を並べ替えようとした場合に発生する次のランタイム例外

この例外は、GridView の SupplierName BoundField の SortExpressionSupplierName に設定されているために発生します。 しかし、Suppliers テーブルのサプライヤーの名前は、実際にはこの列名に SupplierName という別名を付けた CompanyName が呼び出されます。 ただし、ROW_NUMBER() 関数で使用される OVER 句はエイリアスを使用することはできず、実際の列名を使用する必要があります。 そのため、SupplierName BoundField の SortExpression を SupplierName から CompanyName に変更します (図 9 参照)。 図 10 に示すように、この変更後、結果はサプライヤーごとに並べ替えることができます。

Change the SupplierName BoundField s SortExpression to CompanyName

図 9: SupplierName BoundField の SortExpression の CompanyName への変更

The Results Can Now Be Sorted by Supplier

図 10: サプライヤーごとに並べ替えられるようになった結果 (クリックするとフルサイズの画像が表示されます)

まとめ

前のチュートリアルで確認したカスタム ページングの実装では、デザイン時に結果を並べ替える順序を指定する必要がありました。 つまり、実装したカスタム ページングの実装では、並べ替え機能を同時に提供することはできませんでした。 このチュートリアルでは、最初のストアド プロシージャを拡張して、結果を並べ替えることができるように @sortExpression 入力パラメーターを含めることで、この制限を克服しました。

このストアド プロシージャを作成し、DAL と BLL で新しいメソッドを作成した後、ObjectDataSource を構成して GridView の現在の SortExpression プロパティを BLL SelectMethod に渡すことで、並べ替えとカスタム ページングの両方を提供する GridView を実装できました。

プログラミングに満足!

著者について

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

特別な感謝

このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Carlos Santos でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。