一括更新 (C#)
1 回の操作で複数のデータベース レコードを更新する方法について説明します。 ユーザー インターフェイス レイヤーでは、各行が編集可能な GridView を作成します。 データ アクセス レイヤーでは、1 つのトランザクション内に複数の更新操作をラップして、すべての更新が成功するか、すべての更新がロールバックされるようにします。
はじめに
前のチュートリアルでは、データ アクセス レイヤーを拡張してデータベース トランザクションのサポートを追加する方法について説明しました。 データベース トランザクションでは、一連のデータ変更ステートメントが 1 つのアトミック操作として処理されることが保証されます。これにより、すべての変更が失敗するか、すべて成功するかのいずれかになります。 この低レベルの DAL 機能について説明したので、データのバッチ変更インターフェイスの作成に注意を向ける準備が整いました。
このチュートリアルでは、各行が編集可能な GridView を作成します (図 1 を参照)。 各行はその編集インターフェイスでレンダリングされるため、[Edit]、[Update]、[Cancel] ボタンの列は必要ありません。 代わりに、ページ上に 2 つの [Update Products] ボタンがあります。これらをクリックすると、GridView の行が列挙され、データベースが更新されます。
図 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 の ID
を ProductsGrid
に設定し、スマート タグから ProductsDataSource
という名前の新しい ObjectDataSource にバインドすることを選択します。 ProductsBLL
クラスの GetProducts
メソッドからデータを取得するように ObjectDataSource を構成します。
図 2: ProductsBLL
クラスを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
図 3: GetProducts
メソッドを使用して製品データを取得する (クリックするとフルサイズの画像が表示されます)
GridView と同様に、ObjectDataSource の変更機能は行ごとに機能するように設計されています。 レコードのセットを更新するには、ASP.NET ページの分離コード クラスに、データをバッチ処理して BLL に渡す少量のコードを記述する必要があります。 そのため、ObjectDataSource の [UPDATE]、[INSERT]、[DELETE] タブのドロップダウン リストを [(なし)] に設定します。 [完了] をクリックして、ウィザードを完了します。
図 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 が作成されます。 このチュートリアルでは、製品の名前、カテゴリ、価格、廃止済みの状態のみをユーザーが表示および編集できるようにします。 ProductName
、CategoryName
、UnitPrice
、Discontinued
以外のすべてのフィールドを削除し、最初の 3 つのフィールドの HeaderText
プロパティをそれぞれ Product、Category、Price に変更します。 最後に、GridView のスマート タグで [ページングを有効にする] チェックボックスと [並べ替えを有効にする] チェックボックスをオンにします。
この時点で、GridView には 3 つの BoundField (ProductName
、CategoryName
、UnitPrice
) と 1 つの CheckBoxField (Discontinued
) があります。 これら 4 つのフィールドを TemplateField に変換し、編集インターフェイスを TemplateField の EditItemTemplate
からその ItemTemplate
に移動する必要があります。
Note
TemplateField の作成とカスタマイズについては、「データ変更インターフェイスをカスタマイズする」チュートリアルで説明しました。 ここでは BoundField と CheckBoxField を TemplateField に変換し、それらの編集インターフェイスを ItemTemplate
で定義する手順について説明しますが、行き詰まった場合や復習が必要な場合は、ためらわずにこの前のチュートリアルを参照してください。
GridView のスマート タグで、[列の編集] リンクをクリックして [フィールド] ダイアログ ボックスを開きます。 次に、各フィールドを選択し、[このフィールドを TemplateField に変換する] リンクをクリックします。
図 5: 既存の BoundField と CheckBoxField を TemplateField に変換する
これで各フィールドが TemplateField になったので、編集インターフェイスを EditItemTemplate
から ItemTemplate
に移動する準備ができました。
手順 2: ProductName
、UnitPrice
、Discontinued
編集インターフェイスの作成
この手順では、ProductName
、UnitPrice
、Discontinued
編集インターフェイスの作成を扱います。各インターフェイスは 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 のようになるはずです。
図 6: ProductName
TemplateField に TextBox と RequiredFieldValidator が含まれるようになった (クリックするとフルサイズの画像が表示されます)
UnitPrice
編集インターフェイスについては、まず TextBox を EditItemTemplate
から ItemTemplate
にコピーします。 次に、TextBox の前に $ を置き、その ID
プロパティを UnitPrice に設定し、その Columns
プロパティを 8 に設定します。
また、UnitPrice
の ItemTemplate
に 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 に設定します。
図 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
にドラッグし、その ID
を Categories
に設定します。 この時点で、通常はスマート タグを使って DropDownList のデータ ソースを定義し、新しい ObjectDataSource を作成します。 しかし、そうすると ItemTemplate
内に ObjectDataSource が追加され、GridView の行ごとに ObjectDataSource インスタンスが作成されてしまいます。 代わりに、GridView の TemplateField の外部で ObjectDataSource を作成しましょう。 テンプレートの編集を終了し、ObjectDataSource をツールボックスから ProductsDataSource
ObjectDataSource の下のデザイナーにドラッグします。 新しい ObjectDataSource に CategoriesDataSource
という名前を付け、CategoriesBLL
クラスの GetCategories
メソッドを使用するように構成します。
図 8: CategoriesBLL
クラスを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
図 9: GetCategories
メソッドを使用してカテゴリ データを取得する (クリックするとフルサイズの画像が表示されます)
この ObjectDataSource はデータを取得するためにしか使わないので、[UPDATE] タブと [DELETE] タブのドロップダウン リストを [(なし)] に設定します。 [完了] をクリックして、ウィザードを完了します。
図 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
を使うように選択します。
図 11: DropDownList を CategoriesDataSource
にバインドする (クリックするとフルサイズの画像が表示されます)
この時点で、Categories
DropDownList はすべてのカテゴリを一覧表示しますが、GridView の行にバインドされている製品に適したカテゴリはまだ自動的に選択されません。 これを実現するには、Categories
DropDownList の SelectedValue
を製品の CategoryID
値に設定する必要があります。 図 12 に示すように、DropDownList のスマート タグから [DataBindings の編集] リンクをクリックし、SelectedValue
プロパティを CategoryID
データ フィールドに関連付けます。
図 12: 製品の CategoryID
値を DropDownList の SelectedValue
プロパティにバインドする
最後に問題が 1 つ残っています。製品に CategoryID
値が指定されていない場合、SelectedValue
のデータ バインド ステートメントでは例外が発生します。 これは、DropDownList にはカテゴリの項目のみが含まれ、CategoryID
が NULL
データベース値である製品のためのオプションは用意されていないためです。 これを解決するには、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
を使ってレンダリングされ、これにはそのセルの編集インターフェイスが含まれています。
図 13: GridView の各行が編集可能 (クリックするとフルサイズの画像が表示されます)
書式設定に関する小さな問題がいくつかあり、この時点で対処しておく必要があります。 まず、UnitPrice
の値に小数点以下 4 桁まで含まれています。 これを修正するには、UnitPrice
TemplateField の ItemTemplate
に戻り、TextBox のスマート タグから [DataBindings の編集] リンクをクリックします。 次に、Text
プロパティを数値として書式設定するように指定します。
図 14: Text
プロパティを数値として書式設定する
次に、Discontinued
列のチェックボックスを (左揃えではなく) 中央揃えにしましょう。 GridView のスマート タグから [列の編集] をクリックし、左下隅にあるフィールドの一覧から Discontinued
TemplateField を選択します。 図 15 に示すように、ItemStyle
にドリルダウンして HorizontalAlign
プロパティを Center に設定します。
図 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 コントロールを追加し、書式設定を変更した後にブラウザーで表示したこのページを示しています。
図 16: ページに 2 つの [Update Products] ボタンが追加された (クリックするとフルサイズの画像が表示されます)
手順 5: 製品の更新
このページにアクセスしたユーザーは、各自の変更を加えて、2 つの [Update Products] ボタンのいずれかをクリックします。 その時点で、ユーザーが各行に入力した値を何らかの方法で ProductsDataTable
インスタンスに保存し、それを BLL メソッドに渡す必要があります。BLL メソッドはその ProductsDataTable
インスタンスを DAL の UpdateWithTransaction
メソッドに渡します。 前のチュートリアルで作成した UpdateWithTransaction
メソッドでは、変更のバッチがアトミック操作として更新されることが保証されます。
BatchUpdate.aspx.cs
に BatchUpdate
という名前のメソッドを作成し、次のコードを追加します。
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
メソッドと BatchUpdate
の foreach
ループ内のコードは同じです。
このチュートリアルを完了するには、いずれかの [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
インスタンスの RowState
を Modified
に設定してから、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行をドロップしてください。