Share via


一括更新 (C#)

作成者: Scott Mitchell

PDF のダウンロード

1 回の操作で複数のデータベース レコードを更新する方法について説明します。 ユーザー インターフェイス レイヤーでは、各行が編集可能な GridView を作成します。 データ アクセス レイヤーでは、1 つのトランザクション内に複数の更新操作をラップして、すべての更新が成功するか、すべての更新がロールバックされるようにします。

はじめに

前のチュートリアルでは、データ アクセス レイヤーを拡張してデータベース トランザクションのサポートを追加する方法について説明しました。 データベース トランザクションでは、一連のデータ変更ステートメントが 1 つのアトミック操作として処理されることが保証されます。これにより、すべての変更が失敗するか、すべて成功するかのいずれかになります。 この低レベルの DAL 機能について説明したので、データのバッチ変更インターフェイスの作成に注意を向ける準備が整いました。

このチュートリアルでは、各行が編集可能な GridView を作成します (図 1 を参照)。 各行はその編集インターフェイスでレンダリングされるため、[Edit]、[Update]、[Cancel] ボタンの列は必要ありません。 代わりに、ページ上に 2 つの [Update Products] ボタンがあります。これらをクリックすると、GridView の行が列挙され、データベースが更新されます。

Each Row in the GridView is Editable

図 1: GridView の各行が編集可能 (クリックするとフルサイズの画像が表示されます)

では、始めましょう。

Note

バッチ更新を実行する」チュートリアルでは、DataList コントロールを使ってバッチ編集インターフェイスを作成しました。 このチュートリアルでは前のチュートリアルとは異なり、GridView を使用し、トランザクションのスコープ内でバッチ更新を実行します。 このチュートリアルを完了したら、以前のチュートリアルに戻り、前のチュートリアルで追加したデータベース トランザクション関連の機能を使うように更新することをお勧めします。

GridView のすべての行を編集可能にする手順の確認

データの挿入、更新、削除の概要」チュートリアルで説明したように、GridView には、基になるデータを行ごとに編集するための組み込みのサポートが用意されています。 GridView は、内部的にはその EditIndex プロパティを使って編集可能な行を記録しています。 GridView はそのデータ ソースにバインドされているため、各行をチェックして、その行のインデックスが EditIndex の値と等しいかどうかを確認します。 等しい場合、その行のフィールドはその編集インターフェイスを使ってレンダリングされます。 BoundField の場合、編集インターフェイスは TextBox であり、その Text プロパティには BoundField の DataField プロパティで指定されたデータ フィールドの値が割り当てられます。 TemplateField の場合、ItemTemplate の代わりに EditItemTemplate が使用されます。

ユーザーが行の [Edit] ボタンをクリックすると編集ワークフローが開始することを思い出してください。 これによりポストバックが発生し、GridView の EditIndex プロパティがクリックされた行のインデックスに設定され、データがグリッドに再バインドされます。 行の [Cancel] ボタンがクリックされると、ポストバック時に、データをグリッドに再バインドする前に EditIndex-1 の値に設定されます。 GridView の行は 0 からインデックスを開始するため、EditIndex-1 に設定すると、GridView は読み取り専用モードで表示されます。

EditIndex プロパティは行ごとの編集には適していますが、バッチ編集用には設計されていません。 GridView 全体を編集可能にするには、各行をその編集インターフェイスを使ってレンダリングする必要があります。 これを実現する最も簡単な方法は、編集可能な各フィールドを TemplateField として実装し、その編集インターフェイスを ItemTemplate で定義するように作成することです。

次のいくつかの手順で、完全に編集可能な GridView を作成します。 手順 1 では、まず GridView とその ObjectDataSource を作成し、その BoundField と CheckBoxField を TemplateField に変換します。 手順 2 と 3 では、編集インターフェイスを TemplateField の EditItemTemplate からそれらの ItemTemplate フィールドに移動します。

手順 1: 製品情報の表示

行が編集可能な GridView の作成について考える前に、まずシンプルに製品情報を表示してみましょう。 BatchData フォルダー内の BatchUpdate.aspx ページを開き、ツールボックスからデザイナーに GridView をドラッグします。 GridView の IDProductsGrid に設定し、スマート タグから ProductsDataSource という名前の新しい ObjectDataSource にバインドすることを選択します。 ProductsBLL クラスの GetProducts メソッドからデータを取得するように ObjectDataSource を構成します。

Configure the ObjectDataSource to Use the ProductsBLL Class

図 2: ProductsBLL クラスを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)

Retrieve the Product Data Using the GetProducts Method

図 3: GetProducts メソッドを使用して製品データを取得する (クリックするとフルサイズの画像が表示されます)

GridView と同様に、ObjectDataSource の変更機能は行ごとに機能するように設計されています。 レコードのセットを更新するには、ASP.NET ページの分離コード クラスに、データをバッチ処理して BLL に渡す少量のコードを記述する必要があります。 そのため、ObjectDataSource の [UPDATE]、[INSERT]、[DELETE] タブのドロップダウン リストを [(なし)] に設定します。 [完了] をクリックして、ウィザードを完了します。

Set the Drop-Down Lists in the UPDATE, INSERT, and DELETE Tabs to (None)

図 4: [UPDATE]、[INSERT]、[DELETE] の各タブのドロップダウン リストを [(なし)] に設定する (クリックするとフルサイズの画像が表示されます)

[データ ソースの構成] ウィザードを完了すると、ObjectDataSource の宣言型マークアップは次のようになります。

<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>

また、[データ ソースの構成] ウィザードを完了すると、Visual Studio によって GridView の製品データ フィールドの BoundField と CheckBoxField が作成されます。 このチュートリアルでは、製品の名前、カテゴリ、価格、廃止済みの状態のみをユーザーが表示および編集できるようにします。 ProductNameCategoryNameUnitPriceDiscontinued 以外のすべてのフィールドを削除し、最初の 3 つのフィールドの HeaderText プロパティをそれぞれ Product、Category、Price に変更します。 最後に、GridView のスマート タグで [ページングを有効にする] チェックボックスと [並べ替えを有効にする] チェックボックスをオンにします。

この時点で、GridView には 3 つの BoundField (ProductNameCategoryNameUnitPrice) と 1 つの CheckBoxField (Discontinued) があります。 これら 4 つのフィールドを TemplateField に変換し、編集インターフェイスを TemplateField の EditItemTemplate からその ItemTemplate に移動する必要があります。

Note

TemplateField の作成とカスタマイズについては、「データ変更インターフェイスをカスタマイズする」チュートリアルで説明しました。 ここでは BoundField と CheckBoxField を TemplateField に変換し、それらの編集インターフェイスを ItemTemplate で定義する手順について説明しますが、行き詰まった場合や復習が必要な場合は、ためらわずにこの前のチュートリアルを参照してください。

GridView のスマート タグで、[列の編集] リンクをクリックして [フィールド] ダイアログ ボックスを開きます。 次に、各フィールドを選択し、[このフィールドを TemplateField に変換する] リンクをクリックします。

Convert the Existing BoundFields and CheckBoxField Into TemplateField

図 5: 既存の BoundField と CheckBoxField を TemplateField に変換する

これで各フィールドが TemplateField になったので、編集インターフェイスを EditItemTemplate から ItemTemplate に移動する準備ができました。

手順 2: ProductNameUnitPriceDiscontinued 編集インターフェイスの作成

この手順では、ProductNameUnitPriceDiscontinued 編集インターフェイスの作成を扱います。各インターフェイスは TemplateField の EditItemTemplate で既に定義されているため、非常に簡単です。 CategoryName 編集インターフェイスの作成については、該当するカテゴリの DropDownList を作成する必要があるため、少し複雑になります。 この CategoryName 編集インターフェイスには、手順 3 で取り組みます。

まずは ProductName TemplateField から始めましょう。 GridView のスマート タグから [テンプレートの編集] リンクをクリックし、ProductName TemplateField の EditItemTemplate にドリルダウンします。 TextBox を選択してクリップボードにコピーし、ProductName TemplateField の ItemTemplate に貼り付けます。 TextBox の ID プロパティを ProductName に変更します。

次に、ItemTemplate に RequiredFieldValidator を追加して、ユーザーが必ず各製品名の値を指定するようにします。 ControlToValidate プロパティを ProductName に設定し、ErrorMessage プロパティを "You must provide the product's name." (製品名を指定する必要があります。) に設定し、 Text プロパティを * に設定します。 ItemTemplate にこれらの追加を行ったら、画面は図 6 のようになるはずです。

The ProductName TemplateField Now Includes a TextBox and a RequiredFieldValidator

図 6: ProductName TemplateField に TextBox と RequiredFieldValidator が含まれるようになった (クリックするとフルサイズの画像が表示されます)

UnitPrice 編集インターフェイスについては、まず TextBox を EditItemTemplate から ItemTemplate にコピーします。 次に、TextBox の前に $ を置き、その ID プロパティを UnitPrice に設定し、その Columns プロパティを 8 に設定します。

また、UnitPriceItemTemplate に CompareValidator を追加して、ユーザーが入力した値が $0.00 以上の有効な通貨の値であることを確認します。 Validator の ControlToValidate プロパティを UnitPrice に設定し、その ErrorMessage プロパティを "You must enter a valid currency value. Please omit any currency symbols." (有効な通貨の値を入力する必要があります。通貨記号は省略してください。) に設定し、その Text プロパティを * に、Type プロパティを Currency に、Operator プロパティを GreaterThanEqual に、ValueToCompare プロパティを 0 に設定します。

Add a CompareValidator to Ensure the Price Entered is a Non-Negative Currency Value

図 7: CompareValidator を追加して入力された価格が負でない通貨の値であることを確認する (クリックするとフルサイズの画像が表示されます)

Discontinued TemplateField については、ItemTemplate で既に定義されている CheckBox を使用できます。 その ID を Discontinued に設定し、その Enabled プロパティを true に設定するだけです。

手順 3: CategoryName 編集インターフェイスの作成

CategoryName TemplateField の EditItemTemplate の編集インターフェイスには、CategoryName データ フィールドの値を表示する TextBox が含まれています。 これを、使用できるカテゴリを一覧表示する DropDownList に置き換える必要があります。

Note

データ変更インターフェイスをカスタマイズする」チュートリアルでは、TextBox ではなく DropDownList を含むようにテンプレートをカスタマイズする方法についてより詳しく説明されています。 ここで示す手順は完全なものですが、簡略化されています。 カテゴリの DropDownList を作成および構成する方法について詳しくは、もう一度「データ変更インターフェイスをカスタマイズする」チュートリアルを参照してください。

DropDownList をツールボックスから CategoryName TemplateField の ItemTemplate にドラッグし、その IDCategories に設定します。 この時点で、通常はスマート タグを使って DropDownList のデータ ソースを定義し、新しい ObjectDataSource を作成します。 しかし、そうすると ItemTemplate 内に ObjectDataSource が追加され、GridView の行ごとに ObjectDataSource インスタンスが作成されてしまいます。 代わりに、GridView の TemplateField の外部で ObjectDataSource を作成しましょう。 テンプレートの編集を終了し、ObjectDataSource をツールボックスから ProductsDataSource ObjectDataSource の下のデザイナーにドラッグします。 新しい ObjectDataSource に CategoriesDataSource という名前を付け、CategoriesBLL クラスの GetCategories メソッドを使用するように構成します。

Configure the ObjectDataSource to Use the CategoriesBLL Class

図 8: CategoriesBLL クラスを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)

Retrieve the Category Data Using the GetCategories Method

図 9: GetCategories メソッドを使用してカテゴリ データを取得する (クリックするとフルサイズの画像が表示されます)

この ObjectDataSource はデータを取得するためにしか使わないので、[UPDATE] タブと [DELETE] タブのドロップダウン リストを [(なし)] に設定します。 [完了] をクリックして、ウィザードを完了します。

Set the Drop-Down Lists in the UPDATE and DELETE Tabs to (None)

図 10: [UPDATE] タブと [DELETE] タブのドロップダウン リストを [(なし)] に設定します (クリックするとフルサイズの画像が表示されます)

ウィザードを完了すると、CategoriesDataSource の宣言型マークアップは次のようになります。

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

CategoriesDataSource を作成して構成したので、CategoryName TemplateField の ItemTemplate に戻り、DropDownList のスマート タグから [データ ソースの選択] リンクをクリックします。 データ ソース構成ウィザードで、最初のドロップダウン リストから CategoriesDataSource オプションを選択し、表示に CategoryName を使い、値として CategoryID を使うように選択します。

Bind the DropDownList to the CategoriesDataSource

図 11: DropDownList を CategoriesDataSource にバインドする (クリックするとフルサイズの画像が表示されます)

この時点で、Categories DropDownList はすべてのカテゴリを一覧表示しますが、GridView の行にバインドされている製品に適したカテゴリはまだ自動的に選択されません。 これを実現するには、Categories DropDownList の SelectedValue を製品の CategoryID 値に設定する必要があります。 図 12 に示すように、DropDownList のスマート タグから [DataBindings の編集] リンクをクリックし、SelectedValue プロパティを CategoryID データ フィールドに関連付けます。

Bind the Product s CategoryID Value to the DropDownList s SelectedValue Property

図 12: 製品の CategoryID 値を DropDownList の SelectedValue プロパティにバインドする

最後に問題が 1 つ残っています。製品に CategoryID 値が指定されていない場合、SelectedValue のデータ バインド ステートメントでは例外が発生します。 これは、DropDownList にはカテゴリの項目のみが含まれ、CategoryIDNULL データベース値である製品のためのオプションは用意されていないためです。 これを解決するには、DropDownList の AppendDataBoundItems プロパティを true に設定し、新しい項目を DropDownList に追加し、宣言構文から Value プロパティを省略します。 つまり、Categories DropDownList の宣言構文を次のようにします。

<asp:DropDownList ID="Categories" runat="server" AppendDataBoundItems="True" 
    DataSourceID="CategoriesDataSource" DataTextField="CategoryName" 
    DataValueField="CategoryID" SelectedValue='<%# Bind("CategoryID") %>'>
    <asp:ListItem Value=">-- Select One --</asp:ListItem>
</asp:DropDownList>

<asp:ListItem Value=""> -- Select One -- で、Value 属性が明示的に空の文字列に設定されていることに注意してください。 NULL のケースを処理するためにこの追加の DropDownList 項目が必要な理由と、Value プロパティに空の文字列を割り当てることが不可欠な理由について詳しくは、もう一度「データ変更インターフェイスをカスタマイズする」チュートリアルを参照してください。

Note

ここでは、パフォーマンスとスケーラビリティに関する問題が発生する可能性があるため、注意が必要です。 CategoriesDataSource をデータ ソースとして使う DropDownList が各行に含まれるため、CategoriesBLL クラスの GetCategories メソッドはページ アクセスごとに n 回呼び出されます。n は GridView の行数です。 こうした n 回の GetCategories の呼び出しにより、データベースに対するクエリが n 回発生します。 このデータベースへの影響は、返されるカテゴリを、SQL キャッシュ依存関係または非常に短い期間の有効期限を使って、要求ごとのキャッシュでキャッシュするか、キャッシュ レイヤーを介してキャッシュすることで軽減できる場合があります。

手順 4: 編集インターフェイスを完成させる

途中で進行状況を確認せずに、多数の変更を GridView のテンプレートに加えてきました。 少し時間を取って、ブラウザーで進行状況を確認してみましょう。 図 13 に示すように、各行はその ItemTemplate を使ってレンダリングされ、これにはそのセルの編集インターフェイスが含まれています。

Each GridView Row is Editable

図 13: GridView の各行が編集可能 (クリックするとフルサイズの画像が表示されます)

書式設定に関する小さな問題がいくつかあり、この時点で対処しておく必要があります。 まず、UnitPrice の値に小数点以下 4 桁まで含まれています。 これを修正するには、UnitPrice TemplateField の ItemTemplate に戻り、TextBox のスマート タグから [DataBindings の編集] リンクをクリックします。 次に、Text プロパティを数値として書式設定するように指定します。

Format the Text Property as a Number

図 14: Text プロパティを数値として書式設定する

次に、Discontinued 列のチェックボックスを (左揃えではなく) 中央揃えにしましょう。 GridView のスマート タグから [列の編集] をクリックし、左下隅にあるフィールドの一覧から Discontinued TemplateField を選択します。 図 15 に示すように、ItemStyle にドリルダウンして HorizontalAlign プロパティを Center に設定します。

Center the Discontinued CheckBox

図 15: Discontinued CheckBox の中央揃え

次に、ValidationSummary コントロールをページに追加し、その ShowMessageBox プロパティを true に、ShowSummary プロパティを false に設定します。 また、クリックするとユーザーの変更内容が更新される Button Web コントロールを追加します。 具体的には、GridView の上と下に 1 つずつ、2 つの Button Web コントロールを追加し、両方のコントロールの Text プロパティを "Update Products" に設定します。

GridView の編集インターフェイスはその TemplateField の ItemTemplate で定義されており、EditItemTemplate は余分なので、削除しても構いません。

上述の書式設定の変更を行い、Button コントロールを追加し、不要な EditItemTemplate を削除したら、ページの宣言構文は次のようになります。

<p>
    <asp:Button ID="UpdateAllProducts1" runat="server" Text="Update Products" />
</p>
<p>
    <asp:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" 
        DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
        AllowPaging="True" AllowSorting="True">
        <Columns>
            <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
                <ItemTemplate>
                    <asp:TextBox ID="ProductName" runat="server" 
                        Text='<%# Bind("ProductName") %>'></asp:TextBox>
                    <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
                        ControlToValidate="ProductName"
                        ErrorMessage="You must provide the product's name." 
                        runat="server">*</asp:RequiredFieldValidator>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Category" 
                SortExpression="CategoryName">
                <ItemTemplate>
                    <asp:DropDownList ID="Categories" runat="server" 
                        AppendDataBoundItems="True" 
                        DataSourceID="CategoriesDataSource"
                        DataTextField="CategoryName" 
                        DataValueField="CategoryID" 
                        SelectedValue='<%# Bind("CategoryID") %>'>
                        <asp:ListItem>-- Select One --</asp:ListItem>
                    </asp:DropDownList>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Price" 
                SortExpression="UnitPrice">
                <ItemTemplate>
                    $<asp:TextBox ID="UnitPrice" runat="server" Columns="8" 
                        Text='<%# Bind("UnitPrice", "{0:N}") %>'></asp:TextBox>
                    <asp:CompareValidator ID="CompareValidator1" runat="server" 
                        ControlToValidate="UnitPrice"
                        ErrorMessage="You must enter a valid currency value. 
                                      Please omit any currency symbols."
                        Operator="GreaterThanEqual" Type="Currency" 
                        ValueToCompare="0">*</asp:CompareValidator>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued">
                <ItemTemplate>
                    <asp:CheckBox ID="Discontinued" runat="server" 
                        Checked='<%# Bind("Discontinued") %>' />
                </ItemTemplate>
                <ItemStyle HorizontalAlign="Center" />
            </asp:TemplateField>
        </Columns>
    </asp:GridView>
</p>
<p>
    <asp:Button ID="UpdateAllProducts2" runat="server" Text="Update Products" />
    <asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
        OldValuesParameterFormatString="original_{0}"
        SelectMethod="GetProducts" TypeName="ProductsBLL">
    </asp:ObjectDataSource>
    <asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
        OldValuesParameterFormatString="original_{0}"
        SelectMethod="GetCategories" TypeName="CategoriesBLL">
    </asp:ObjectDataSource>
    <asp:ValidationSummary ID="ValidationSummary1" runat="server" 
        ShowMessageBox="True" ShowSummary="False" />
</p>

図 16 は、Button Web コントロールを追加し、書式設定を変更した後にブラウザーで表示したこのページを示しています。

The Page Now Includes Two Update Products Buttons

図 16: ページに 2 つの [Update Products] ボタンが追加された (クリックするとフルサイズの画像が表示されます)

手順 5: 製品の更新

このページにアクセスしたユーザーは、各自の変更を加えて、2 つの [Update Products] ボタンのいずれかをクリックします。 その時点で、ユーザーが各行に入力した値を何らかの方法で ProductsDataTable インスタンスに保存し、それを BLL メソッドに渡す必要があります。BLL メソッドはその ProductsDataTable インスタンスを DAL の UpdateWithTransaction メソッドに渡します。 前のチュートリアルで作成した UpdateWithTransaction メソッドでは、変更のバッチがアトミック操作として更新されることが保証されます。

BatchUpdate.aspx.csBatchUpdate という名前のメソッドを作成し、次のコードを追加します。

private void BatchUpdate()
{
    // Enumerate the GridView's Rows collection and create a ProductRow
    ProductsBLL productsAPI = new ProductsBLL();
    Northwind.ProductsDataTable products = productsAPI.GetProducts();
    foreach (GridViewRow gvRow in ProductsGrid.Rows)
    {
        // Find the ProductsRow instance in products that maps to gvRow
        int productID = Convert.ToInt32(ProductsGrid.DataKeys[gvRow.RowIndex].Value);
        Northwind.ProductsRow product = products.FindByProductID(productID);
        if (product != null)
        {
            // Programmatically access the form field elements in the 
            // current GridViewRow
            TextBox productName = (TextBox)gvRow.FindControl("ProductName");
            DropDownList categories = 
                (DropDownList)gvRow.FindControl("Categories");
            TextBox unitPrice = (TextBox)gvRow.FindControl("UnitPrice");
            CheckBox discontinued = 
                (CheckBox)gvRow.FindControl("Discontinued");
            // Assign the user-entered values to the current ProductRow
            product.ProductName = productName.Text.Trim();
            if (categories.SelectedIndex == 0) 
                product.SetCategoryIDNull(); 
            else 
                product.CategoryID = Convert.ToInt32(categories.SelectedValue);
            if (unitPrice.Text.Trim().Length == 0) 
                product.SetUnitPriceNull(); 
            else 
                product.UnitPrice = Convert.ToDecimal(unitPrice.Text);
            product.Discontinued = discontinued.Checked;
        }
    }
    // Now have the BLL update the products data using a transaction
    productsAPI.UpdateWithTransaction(products);
}

このメソッドは、まず BLL の GetProducts メソッドを呼び出して、すべての製品を ProductsDataTable に戻します。 次に、ProductGrid GridView の Rows コレクションを列挙します。 Rows コレクションには、GridView に表示される各行の GridViewRow インスタンスが含まれています。 1 ページあたり最大 10 行を表示するため、GridView の Rows コレクションには 10 個を超える項目は含まれません。

各行について、DataKeys コレクションから ProductID を取得し、ProductsDataTable から適切な ProductsRow を選択します。 4 つの TemplateField 入力コントロールをプログラムによって参照し、その値を ProductsRow インスタンスのプロパティに割り当てます。 GridView の各行の値を使って ProductsDataTable を更新してから、BLL の UpdateWithTransaction メソッドに渡します。これは、前のチュートリアルで説明したように、DAL の UpdateWithTransaction メソッドを呼び出すだけです。

このチュートリアルで使用しているバッチ更新アルゴリズムでは、製品の情報が変更されたかどうかに関係なく、GridView の行に対応する ProductsDataTable の各行を更新します。 このように無条件で更新しても、通常はパフォーマンス上の問題にはなりませんが、データベース テーブルへの変更を監査する場合は、余計なレコードが増えてしまうおそれがあります。 前の「バッチ更新を実行する」チュートリアルでは、DataList を使ったバッチ更新インターフェイスについて調べ、ユーザーが実際に変更したレコードのみを更新するコードを追加しました。 必要に応じて、このチュートリアルのコードを「バッチ更新を実行する」の手法を使って更新してください。

Note

スマート タグを使って GridView にデータ ソースをバインドすると、Visual Studio によって自動的にデータ ソースの主キー値が GridView の DataKeyNames プロパティに割り当てられます。 手順 1 で説明したように、GridView のスマート タグを使って ObjectDataSource を GridView にバインドしなかった場合は、DataKeys コレクションを使って各行の ProductID 値にアクセスするために、手動で GridView の DataKeyNames プロパティを ProductID に設定する必要があります。

BatchUpdate で使用されるコードは BLL の UpdateProduct メソッドで使用されるコードと似ていますが、主要な違いは、UpdateProduct メソッドではアーキテクチャから 1 つの ProductRow インスタンスのみを取得することです。 ProductRow のプロパティを割り当てるコードは、全体的なパターンとして、UpdateProducts メソッドと BatchUpdateforeach ループ内のコードは同じです。

このチュートリアルを完了するには、いずれかの [Update Products] ボタンがクリックされたときに BatchUpdate メソッドを呼び出す必要があります。 これら 2 つの Button コントロールの Click イベントに対するイベント ハンドラーを作成し、そのイベント ハンドラーに次のコードを追加します。

BatchUpdate();
ClientScript.RegisterStartupScript(this.GetType(), "message", 
    "alert('The products have been updated.');", true);

まず、BatchUpdate を呼び出します。 次に、ClientScript property を使って、"The products have been updated." (製品が更新されました。) というメッセージ ボックスを表示する JavaScript を挿入します。

少し時間を取って、このコードをテストしましょう。 ブラウザーを使って BatchUpdate.aspx にアクセスし、複数の行を編集して、いずれかの [Update Products] ボタンをクリックします。 入力の検証エラーはないと仮定すると、"The products have been updated." (製品が更新されました。) というメッセージ ボックスが表示されるはずです。 更新がアトミックであることを確認するために、適当な CHECK 制約を追加してみましょう。たとえば、1234.56 という UnitPrice 値を許可しない制約です。 その後、BatchUpdate.aspx で複数のレコードを編集し、1 つの製品の UnitPrice 値を許可されていない値 (1234.56) に設定します。 この場合、[Update Products] をクリックするとエラーが発生し、このバッチ操作中のその他の変更は元の値にロールバックされるはずです。

代わりの BatchUpdate メソッド

先ほど説明した BatchUpdate メソッドでは、BLL の GetProducts メソッドから "すべて" の製品を取得し、その後 GridView に表示されるレコードのみを更新します。 この方法は、GridView でページングを使わない場合には最適ですが、ページングを使う場合は、数百、数千、または数万もの製品が存在するのに、GridView には 10 行しか含まれない可能性があります。 このような場合、データベースからすべての製品を取得して、そのうちの 10 個しか変更しないのは理想的とはいえません。

このような状況では、代わりに次の BatchUpdateAlternate メソッドを使うことを検討してください。

private void BatchUpdateAlternate()
{
    // Enumerate the GridView's Rows collection and create a ProductRow
    ProductsBLL productsAPI = new ProductsBLL();
    Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
    foreach (GridViewRow gvRow in ProductsGrid.Rows)
    {
        // Create a new ProductRow instance
        int productID = Convert.ToInt32(ProductsGrid.DataKeys[gvRow.RowIndex].Value);
        
        Northwind.ProductsDataTable currentProductDataTable = 
            productsAPI.GetProductByProductID(productID);
        if (currentProductDataTable.Rows.Count > 0)
        {
            Northwind.ProductsRow product = currentProductDataTable[0];
            // Programmatically access the form field elements in the 
            // current GridViewRow
            TextBox productName = (TextBox)gvRow.FindControl("ProductName");
            DropDownList categories = 
                (DropDownList)gvRow.FindControl("Categories");
            TextBox unitPrice = (TextBox)gvRow.FindControl("UnitPrice");
            CheckBox discontinued = 
                (CheckBox)gvRow.FindControl("Discontinued");
            // Assign the user-entered values to the current ProductRow
            product.ProductName = productName.Text.Trim();
            if (categories.SelectedIndex == 0) 
                product.SetCategoryIDNull(); 
            else 
                product.CategoryID = Convert.ToInt32(categories.SelectedValue);
            if (unitPrice.Text.Trim().Length == 0) 
                product.SetUnitPriceNull(); 
            else 
                product.UnitPrice = Convert.ToDecimal(unitPrice.Text);
            product.Discontinued = discontinued.Checked;
            // Import the ProductRow into the products DataTable
            products.ImportRow(product);
        }
    }
    // Now have the BLL update the products data using a transaction
    productsAPI.UpdateProductsWithTransaction(products);
}

BatchMethodAlternate では最初に、products という名前の新しい空の ProductsDataTable を作成します。 次に、GridView の Rows コレクションをステップ実行し、行ごとに BLL の GetProductByProductID(productID) メソッドを使って特定の製品情報を取得します。 取得した ProductsRow インスタンスのプロパティは BatchUpdate と同じ方法で更新しますが、行を更新した後に、DataTable の ImportRow(DataRow) メソッドを使って products``ProductsDataTable にインポートします。

foreach ループが完了すると、products には GridView の行ごとに 1 つの ProductsRow インスタンスが含まれています。 各 ProductsRow インスタンスは (更新されるのではなく) products に追加されているため、これを無条件で UpdateWithTransaction メソッドに渡すと、ProductsTableAdapter は各レコードをデータベースに挿入しようと試みます。 代わりに、これらの各行が (追加ではなく) 変更されたことを指定する必要があります。

これを実現するために、UpdateProductsWithTransaction という名前の新しいメソッドを BLL に追加します。 以下に示す UpdateProductsWithTransaction では、ProductsDataTable 内の各 ProductsRow インスタンスの RowStateModified に設定してから、ProductsDataTable を DAL の UpdateWithTransaction メソッドに渡しています。

public int UpdateProductsWithTransaction(Northwind.ProductsDataTable products)
{
    // Mark each product as Modified
    products.AcceptChanges();
    foreach (Northwind.ProductsRow product in products)
        product.SetModified();
    // Update the data via a transaction
    return UpdateWithTransaction(products);
}

まとめ

GridView には、行ごとの編集機能が組み込みで用意されていますが、完全に編集可能なインターフェイスの作成はサポートされていません。 このチュートリアルで説明したように、このようなインターフェイスは実現可能ですが、少し作業が必要になります。 すべての行が編集可能な GridView を作成するには、GridView のフィールドを TemplateField に変換し、ItemTemplate 内で編集インターフェイスを定義する必要があります。 さらに、[すべて更新] タイプの Button Web コントロールは、GridView とは別にページに追加する必要があります。 こうした Button の Click イベント ハンドラーでは、GridView の Rows コレクションを列挙し、変更を ProductsDataTable に格納して、更新された情報を適切な BLL メソッドに渡す必要があります。

次のチュートリアルでは、バッチ削除用のインターフェイスを作成する方法について説明します。 具体的には、GridView の各行にチェックボックスを追加し、[すべて更新] タイプのボタンの代わりに、[Delete Selected Rows] (選択した行の削除) ボタンを設定します。

プログラミングに満足!

著者について

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

特別な感謝

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