2 つの DropDownList でマスター/詳細をフィルター処理する (C#)
このチュートリアルでは、マスター/詳細リレーションシップを拡大して 3 番目のレイヤーを追加し、2 つの DropDownList コントロールを使用して、目的の親および親の親レコードを選択します。
はじめに
前のチュートリアルでは、カテゴリが設定された 1 つの DropDownList と、選択したカテゴリに属する製品を表示する GridView を使用して、単純なマスター/詳細レポートを表示する方法を調べました。 このレポート パターンは、1 対多リレーションシップを含むレコードを表示する場合に適しており、複数の 1 対多リレーションシップを含むシナリオで機能するように簡単に拡張できます。 たとえば、注文入力システムには、顧客、注文、および注文の品目に対応するテーブルがあります。 特定の顧客が、各注文が複数の品目で構成される、複数の注文を行っている場合があります。 このようなデータは、2 つの DropDownList と 1 つの GridView を使用してユーザーに表示できます。 1 つ目の DropDownList にはデータベース内の各顧客のリスト項目があり、2 つ目の内容は、選択した顧客によって行われた注文です。 GridView では、選択した注文の品目が一覧表示されます。
Northwind データベースには、その Customers
、Orders
、Order Details
テーブル内に正規の顧客/注文/注文の詳細の情報が含まれていますが、これらのテーブルはこのアーキテクチャにはキャプチャされません。 それでも、2 つの依存する DropDownList の使用について説明することはできます。 1 つ目の DropDownList にはカテゴリが、2 つ目には選択したカテゴリに属する第 2 の製品が、それぞれ一覧表示されます。 これで、選択された製品の詳細が DetailsView に一覧表示されます。
手順 1: カテゴリの DropDownList を作成して内容を設定する
最初のゴールは、カテゴリを一覧表示する DropDownList を追加することです。 これらの手順については前のチュートリアルで詳しく検討しましたが、ここでは要約を示して、全体がわかるようにします。
Filtering
フォルダー内の MasterDetailsDetails.aspx
ページを開き、そのページに DropDownList を追加し、その ID
プロパティを Categories
に設定し、次に、そのスマート タグの [データ ソースの構成] リンクをクリックします。 [Data Source Configuration Wizard] (データ ソース構成ウィザード) で、新しいデータ ソースを選択して追加します。
図 1: DropDownList の新しいデータ ソースを追加する (クリックするとフルサイズの画像が表示されます)
新しいデータ ソースは当然、ObjectDataSource です。 この新しい ObjectDataSource に CategoriesDataSource
という名前を付け、それによって CategoriesBLL
オブジェクトの GetCategories()
メソッドが呼び出されるようにします。
図 2: CategoriesBLL
クラスを選択して使用する (クリックするとフルサイズの画像が表示されます)
図 3: GetCategories()
メソッドを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
ObjectDataSource を構成した後も、Categories
DropDownList にどのデータ ソース フィールドを表示するか、およびリスト項目の値としてどれを構成するかを指定する必要があります。 各リスト項目の表示として CategoryName
フィールドを、値として CategoryID
を設定します。
図 4: DropDownList に CategoryName
フィールドを表示し、値として CategoryID
を使用する (クリックするとフルサイズの画像が表示されます)
この時点で、Categories
テーブルのレコードが入力された DropDownList コントロール (Categories
) があります。 ユーザーが DropDownList から新しいカテゴリを選択すると、手順 2 で作成する製品の DropDownList を更新するためにポストバックが実行される必要が生じます。 そのため、categories
DropDownList のスマート タグから [AutoPostBack を有効にする] オプションをオンにします。
図 5: Categories
DropDownList の [AutoPostBack を有効にする] (クリックするとフルサイズの画像が表示されます)
手順 2: 2 番目の DropDownList で選択したカテゴリの製品を表示する
Categories
DropDownList が完了したら、次の手順では、選択したカテゴリに属する製品の DropDownList を表示します。 これを実現するには、別の DropDownList を ProductsByCategory
という名前のページに追加します。 Categories
DropDownList と同様に、ProductsByCategoryDataSource
という名前の ProductsByCategory
DropDownList の新しい ObjectDataSource を作成します。
図 6: ProductsByCategory
DropDownList の新しいデータ ソースを追加する (クリックするとフルサイズの画像が表示されます)
図 7: ProductsByCategoryDataSource
という名前の新しい ObjectDataSource を作成する (クリックするとフルサイズの画像が表示されます)
ProductsByCategory
DropDownList では、選択したカテゴリに属する製品のみを表示する必要があるため、ObjectDataSource で ProductsBLL
オブジェクトから GetProductsByCategoryID(categoryID)
メソッドを呼び出します。
図 8: ProductsBLL
クラスを選択して使用する (クリックするとフルサイズの画像が表示されます)
図 9: GetProductsByCategoryID(categoryID)
メソッドを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
ウィザードの最後の手順では、categoryID
パラメーターの値を指定する必要があります。 このパラメーターを Categories
DropDownList の選択された項目に割り当てます。
図 10: categoryID
パラメーターの値を Categories
DropDownList からプルする (クリックするとフルサイズの画像が表示されます)
ObjectDataSource が構成されたら、残すは、DropDownList の項目の表示と値に使用されるデータ ソース フィールドを指定することだけです。 ProductName
フィールドを表示し、値として ProductID
フィールドを使用します。
図 11: DropDownList の ListItem
の Text
および Value
プロパティに使用するデータ ソース フィールドを指定する (クリックするとフルサイズの画像が表示されます)
ObjectDataSource と ProductsByCategory
DropDownList が構成されたら、ページには 2 つの DropDownList が表示されます。1 つ目にはすべてのカテゴリが一覧表示され、2 つ目には選択したカテゴリに属する製品が一覧表示されます。 ユーザーが 1 つ目の DropDownList から新しいカテゴリを選択すると、ポストバックが実行され、2 つ目の DropDownList が再バインドされ、新しく選択されたカテゴリに属する製品が表示されます。 図 12 と 13 は、ブラウザーで表示した場合の MasterDetailsDetails.aspx
の様子を示しています。
図 12: 最初にページにアクセスすると、[Beverages] (飲料) カテゴリが選択される (クリックするとフルサイズの画像が表示されます)
図 13: 別のカテゴリを選択すると、新しいカテゴリの製品が表示される (クリックするとフルサイズの画像が表示されます)
現在、productsByCategory
DropDownList が変更された場合、ポストバックは "行われません"。 ただし、選択した製品の詳細を表示するために DetailsView を追加したときは、ポストバックが行われる必要があります (手順 3)。 そのため、productsByCategory
DropDownList のスマート タグから [AutoPostBack を有効にする] チェックボックスをオンにします。
図 14: productsByCategory
DropDownList の AutoPostBack 機能を有効にする (クリックするとフルサイズの画像が表示されます)
手順 3: DetailsView を使用して選択した製品の詳細を表示する
最後の手順では、選択した製品の詳細を DetailsView に表示します。 これを実現するには、DetailsView をページに追加し、その ID
プロパティを ProductDetails
に設定して、そのための新しい ObjectDataSource を作成します。 productID
パラメーターの値として ProductsByCategory
DropDownList の選択した値を使用して、ProductsBLL
クラスの GetProductByProductID(productID)
メソッドからデータをプルするように、この ObjectDataSource を構成します。
図 15: ProductsBLL
クラスを選択して使用する (クリックするとフルサイズの画像が表示されます)
図 16: GetProductByProductID(productID)
メソッドを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
図 17: productID
パラメーターの値を ProductsByCategory
DropDownList からプルする (クリックするとフルサイズの画像が表示されます)
DetailsView で使用可能な任意のフィールドを選択して表示できます。 ProductID
、SupplierID
、CategoryID
フィールドを削除し、残りのフィールドを並べ替えて書式設定することにしました。 さらに、DetailsView の Height
および Width
プロパティをクリアして、指定したサイズに制限するのではなく、データを最適に表示するために必要な幅に DetailsView を拡張できるようにしました。 マークアップ全体は以下のように表示されます。
<asp:DetailsView ID="ProductDetails" runat="server"
AutoGenerateRows="False" DataKeyNames="ProductID"
DataSourceID="ObjectDataSource1" EnableViewState="False">
<Fields>
<asp:BoundField DataField="ProductName"
HeaderText="Product" SortExpression="ProductName" />
<asp:BoundField DataField="CategoryName"
HeaderText="Category" ReadOnly="True"
SortExpression="CategoryName" />
<asp:BoundField DataField="SupplierName"
HeaderText="Supplier" ReadOnly="True"
SortExpression="SupplierName" />
<asp:BoundField DataField="QuantityPerUnit"
HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="UnitPrice"
DataFormatString="{0:c}" HeaderText="Price"
HtmlEncode="False" SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock"
HeaderText="UnitsInStock" SortExpression="Units In Stock" />
<asp:BoundField DataField="UnitsOnOrder"
HeaderText="UnitsOnOrder" SortExpression="Units On Order" />
<asp:BoundField DataField="ReorderLevel"
HeaderText="ReorderLevel" SortExpression="Reorder Level" />
<asp:CheckBoxField DataField="Discontinued"
HeaderText="Discontinued" SortExpression="Discontinued" />
</Fields>
</asp:DetailsView>
時間をとって、ブラウザーで MasterDetailsDetails.aspx
ページを表示してみてください。 一見すると、すべてが希望どおりに動作しているように見えますが、わずかな問題があります。 新しいカテゴリを選択すると、ProductsByCategory
DropDownList は選択したカテゴリの製品を含むように更新されますが、ProductDetails
DetailsView には引き続き以前の製品情報が表示されます。 選択したカテゴリで別の製品を選択すると、DetailsView が更新されます。 さらに、十分にテストした場合、新しいカテゴリを続けて選択する (たとえば、Categories
DropDownList からまず [Beverages] (飲料) を、次に [Condiments] (調味料)、[Confections] (菓子) の順に選択する) と、他のすべてのカテゴリの選択では ProductDetails
DetailsView が更新されることがわかります。
この問題を明確化するために、具体的な例を見てみましょう。 最初にこのページにアクセスすると、[Beverages] (飲料) カテゴリが選択され、関連する製品が ProductsByCategory
DropDownList に読み込まれます。 [Chai] (チャイ) が選択された製品であり、図 18 に示すように ProductDetails
DetailsView にその詳細が表示されます。
図 18: 選択された製品の詳細が DetailsView に表示される (クリックするとフルサイズの画像が表示されます)
カテゴリの選択を [Beverages] (飲料) から [Condiments] (調味料) に変更すると、ポストバックが行われ、それに応じて ProductsByCategory
DropDownList が更新されますが、DetailsView には引き続き [Chai] (チャイ) の詳細が表示されます。
図 19: 前に選択した製品の詳細が引き続き表示される (クリックするとフルサイズの画像が表示されます)
一覧から新しい製品を選択すると、DetailsView が想定どおりに更新されます。 製品を変更した後で新しいカテゴリを選択すると、この場合も DetailsView は更新されません。 ただし、新しい製品を選択する代わりに新しいカテゴリを選択した場合、DetailsView は更新されます。 ここでは、いったい何が起こっているのでしょうか?
問題は、ページのライフサイクルにおけるタイミングの問題です。 ページが要求されると、そのつど、いくつもの手順を経てそれがレンダリングされます。 これらの手順のいずれかにおいて、ObjectDataSource コントロールで、SelectParameters
値のいずれかが変更されたかどうかが確認されます。 その場合、ObjectDataSource にバインドされたデータ Web コントロールでは、その表示を更新する必要があることを認識します。 たとえば、新しいカテゴリが選択されると、ProductsByCategoryDataSource
ObjectDataSource では、そのパラメーター値が変更されたことを検出し、ProductsByCategory
DropDownList では自らを再バインドし、選択されたカテゴリの製品を取得します。
この状況で発生する問題は、ページのライフサイクル中において、変更されたパラメーターがあるかどうかが ObjectDataSources で確認される時点が、関連するデータ Web コントロールの再バインドより "前" になることです。 そのため、新しいカテゴリが選択されると、ProductsByCategoryDataSource
ObjectDataSource では、そのパラメーターの値の変更を検出します。 ただし、ProductDetails
DetailsView によって使用される ObjectDataSource では、こうした変更は認識されません。ProductsByCategory
DropDownList がまだ再バインドされていないためです。 ProductsByCategory
DropDownList では、ライフサイクルの後の方でその ObjectDataSource に再バインドし、新しく選択されたカテゴリの製品を取得します。 ProductsByCategory
DropDownList の値が変更されたとき、ProductDetails
DetailsView の ObjectDataSource では既にパラメーター値の確認を行っているため、DetailsView には前の結果が表示されます。 この相互作用を図 20 に示します。
図 20: ProductDetails
DetailsView の ObjectDataSource で変更が確認された後に ProductsByCategory
DropDownList の値が変更された (クリックするとフルサイズの画像が表示されます)
これに対処するには、ProductsByCategory
DropDownList がバインドされた後に ProductDetails
DetailsView を明示的に再バインドする必要があります。 これを実現するには、ProductsByCategory
DropDownList の DataBound
イベントが発生したときに ProductDetails
DetailsView の DataBind()
メソッドを呼び出します。 MasterDetailsDetails.aspx
ページの分離コード クラスに次のイベント ハンドラー コードを追加します (イベント ハンドラーの追加方法の説明については、「ObjectDataSource のパラメーター値をプログラムで設定する」を参照してください)。
protected void ProductsByCategory_DataBound(object sender, EventArgs e)
{
ProductDetails.DataBind();
}
ProductDetails
DetailsView の DataBind()
メソッドに対するこの明示的な呼び出しが追加されたら、チュートリアルは想定どおりに進みます。 図 21 は、この変更で以前の問題がどのように改善されたかを強調しています。
図 21: ProductsByCategory
DropDownList の DataBound
イベントが発生したときに ProductDetails
DetailsView が明示的に更新される (クリックするとフルサイズの画像が表示されます)
まとめ
DropDownList は、マスターと詳細のレコードの間に 1 対多のリレーションシップがある、マスター/詳細レポートの理想的なユーザー インターフェイス要素として機能します。 前のチュートリアルでは、1 つの DropDownList を使用して、選択されたカテゴリによって表示される製品をフィルター処理する方法について確認しました。 このチュートリアルでは、製品の GridView を DropDownList に置き換え、DetailsView を使用して選択された製品の詳細を表示しました。 このチュートリアルで説明する概念は、顧客、注文、注文の品目など、複数の 1 対多リレーションシップを含むデータ モデルに簡単に拡張できます。 一般に、1 対多リレーションシップのそれぞれの "1" エンティティに対し、常に DropDownList を追加できます。
プログラミングに満足!
著者について
Scott Mitchell は、ASP/ASP.NET に関する 7 冊の書籍の著者であり、4GuysFromRolla.com の設立者でもあります。1998 年以降、Microsoft の Web テクノロジを活用した業務を行っています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は Hilton Giesenow でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。