1 回の操作で複数のデータベース レコードを更新する方法について説明します。 ユーザー インターフェイス レイヤーでは、各行が編集可能な GridView を作成します。 データ アクセス レイヤーでは、1 つのトランザクション内に複数の更新操作をラップして、すべての更新が成功するか、すべての更新がロールバックされるようにします。
はじめに
前の チュートリアル では、データ アクセス層を拡張してデータベース トランザクションのサポートを追加する方法について説明しました。 データベース トランザクションでは、一連のデータ変更ステートメントが 1 つのアトミック操作として処理されることが保証されます。これにより、すべての変更が失敗するか、すべて成功するかのいずれかになります。 この低レベルの DAL 機能について説明したので、データのバッチ変更インターフェイスの作成に注意を向ける準備が整いました。
このチュートリアルでは、各行が編集可能な GridView を作成します (図 1 を参照)。 各行はその編集インターフェイスでレンダリングされるため、[Edit]、[Update]、[Cancel] ボタンの列は必要ありません。 代わりに、ページ上に 2 つの [Update Products] ボタンがあります。これらをクリックすると、GridView の行が列挙され、データベースが更新されます。
図 1: GridView の各行は編集可能です (フルサイズの画像を表示する をクリックします)
では、始めましょう。
注
バッチ更新の実行チュートリアルでは、DataList コントロールを使用してバッチ編集インターフェイスを作成しました。 このチュートリアルでは前のチュートリアルとは異なり、GridView を使用し、トランザクションのスコープ内でバッチ更新を実行します。 このチュートリアルを完了したら、以前のチュートリアルに戻り、前のチュートリアルで追加したデータベース トランザクション関連の機能を使うように更新することをお勧めします。
GridView のすべての行を編集可能にする手順の確認
「データの挿入、更新、および削除の概要」チュートリアルで説明したように、GridView には、基になるデータを行ごとに編集するための組み込みのサポートが用意されています。 内部的には、GridView は、 EditIndex
プロパティを使用して編集可能な行をメモします。 GridView はそのデータ ソースにバインドされているため、各行をチェックして、その行のインデックスが EditIndex
の値と等しいかどうかを確認します。 等しい場合、その行のフィールドはその編集インターフェイスを使ってレンダリングされます。 BoundField の場合、編集インターフェイスは TextBox であり、その Text
プロパティには BoundField の DataField
プロパティで指定されたデータ フィールドの値が割り当てられます。 TemplateField の場合、EditItemTemplate
の代わりに ItemTemplate
が使用されます。
ユーザーが行の [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 の作成について考える前に、まずシンプルに製品情報を表示してみましょう。
BatchUpdate.aspx
フォルダー内の BatchData
ページを開き、ツールボックスからデザイナーに 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] タブの [Drop-Down リスト] を [なし] に設定する (フルサイズの画像を表示する 場合はクリックします)
[データ ソースの構成] ウィザードを完了すると、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
に移動する必要があります。
注
データ変更インターフェイスのカスタマイズチュートリアルで TemplateFields の作成とカスタマイズについて説明しました。 ここでは BoundField と CheckBoxField を TemplateField に変換し、それらの編集インターフェイスを ItemTemplate
で定義する手順について説明しますが、行き詰まった場合や復習が必要な場合は、ためらわずにこの前のチュートリアルを参照してください。
GridView のスマート タグで、[列の編集] リンクをクリックして [フィールド] ダイアログ ボックスを開きます。 次に、各フィールドを選択し、[このフィールドを TemplateField に変換する] リンクをクリックします。
図 5: 既存の BoundFields と CheckBoxField を TemplateFields に変換する
これで各フィールドが 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 には、テキスト ボックスと 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 に置き換える必要があります。
注
データ変更インターフェイスのカスタマイズに関するチュートリアルでは、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: [更新] タブと [削除] タブの Drop-Down リストを [なし] に設定します (フルサイズの画像を表示する 場合はクリックします)
ウィザードを完了すると、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 にはカテゴリの項目のみが含まれ、NULL
が CategoryID
データベース値である製品のためのオプションは用意されていないためです。 これを解決するには、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
属性が明示的に空の文字列に設定されていることに注意してください。
ケースを処理するためにこの追加の DropDownList 項目が必要な理由と、空の文字列への NULL
プロパティの割り当てが不可欠な理由については、「Value
」チュートリアルを参照してください。
注
ここでは、パフォーマンスとスケーラビリティに関する問題が発生する可能性があるため、注意が必要です。 各行には 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 つの更新プログラム製品ボタンが含まれるようになりました (フルサイズの画像を表示する をクリックします)
手順 5: 製品の更新
このページにアクセスしたユーザーは、各自の変更を加えて、2 つの [Update Products] ボタンのいずれかをクリックします。 その時点で、ユーザーが各行に入力した値を何らかの方法で ProductsDataTable
インスタンスに保存し、それを BLL メソッドに渡す必要があります。BLL メソッドはその ProductsDataTable
インスタンスを DAL の UpdateWithTransaction
メソッドに渡します。
UpdateWithTransaction
で作成した メソッドを使用すると、変更のバッチがアトミック操作として更新されます。
BatchUpdate
に BatchUpdate.aspx.vb
という名前のメソッドを作成し、次のコードを追加します。
Private Sub BatchUpdate()
' Enumerate the GridView's Rows collection and create a ProductRow
Dim productsAPI As New ProductsBLL()
Dim products As Northwind.ProductsDataTable = productsAPI.GetProducts()
For Each gvRow As GridViewRow In ProductsGrid.Rows
' Find the ProductsRow instance in products that maps to gvRow
Dim productID As Integer = _
Convert.ToInt32(ProductsGrid.DataKeys(gvRow.RowIndex).Value)
Dim product As Northwind.ProductsRow = products.FindByProductID(productID)
If product IsNot Nothing Then
' Programmatically access the form field elements in the
' current GridViewRow
Dim productName As TextBox = _
CType(gvRow.FindControl("ProductName"), TextBox)
Dim categories As DropDownList = _
CType(gvRow.FindControl("Categories"), DropDownList)
Dim unitPrice As TextBox = _
CType(gvRow.FindControl("UnitPrice"), TextBox)
Dim discontinued As CheckBox = _
CType(gvRow.FindControl("Discontinued"), CheckBox)
' Assign the user-entered values to the current ProductRow
product.ProductName = productName.Text.Trim()
If categories.SelectedIndex = 0 Then
product.SetCategoryIDNull()
Else
product.CategoryID = Convert.ToInt32(categories.SelectedValue)
End If
If unitPrice.Text.Trim().Length = 0 Then
product.SetUnitPriceNull()
Else
product.UnitPrice = Convert.ToDecimal(unitPrice.Text)
End If
product.Discontinued = discontinued.Checked
End If
Next
' Now have the BLL update the products data using a transaction
productsAPI.UpdateWithTransaction(products)
End Sub
このメソッドは、まず BLL の ProductsDataTable
メソッドを呼び出して、すべての製品を GetProducts
に戻します。 次に、 ProductGrid
GridView の Rows
コレクションを列挙します。
Rows
コレクションには、GridView に表示される各行のGridViewRow
インスタンスが含まれています。 1 ページあたり最大 10 行を表示するため、GridView の Rows
コレクションには 10 個を超える項目は含まれません。
各行について、ProductID
コレクションから DataKeys
を取得し、ProductsRow
から適切な ProductsDataTable
を選択します。 4 つの TemplateField 入力コントロールをプログラムによって参照し、その値を ProductsRow
インスタンスのプロパティに割り当てます。 GridView の各行の値を使って ProductsDataTable
を更新してから、BLL の UpdateWithTransaction
メソッドに渡します。これは、前のチュートリアルで説明したように、DAL の UpdateWithTransaction
メソッドを呼び出すだけです。
このチュートリアルで使用しているバッチ更新アルゴリズムでは、製品の情報が変更されたかどうかに関係なく、GridView の行に対応する ProductsDataTable
の各行を更新します。 このように無条件で更新しても、通常はパフォーマンス上の問題にはなりませんが、データベース テーブルへの変更を監査する場合は、余計なレコードが増えてしまうおそれがあります。
バッチ更新の実行チュートリアルに戻り、DataList を使用してバッチ更新インターフェイスを調べ、ユーザーが実際に変更したレコードのみを更新するコードを追加しました。 必要に応じて、 バッチ更新を実行する 方法を自由に使用して、このチュートリアルのコードを更新してください。
注
スマート タグを使って GridView にデータ ソースをバインドすると、Visual Studio によって自動的にデータ ソースの主キー値が GridView の DataKeyNames
プロパティに割り当てられます。 手順 1 で説明したように、GridView のスマート タグを使って ObjectDataSource を GridView にバインドしなかった場合は、DataKeyNames
コレクションを使って各行の ProductID
値にアクセスするために、手動で GridView の DataKeys
プロパティを ProductID に設定する必要があります。
BatchUpdate
で使用されるコードは BLL の UpdateProduct
メソッドで使用されるコードと似ていますが、主要な違いは、UpdateProduct
メソッドではアーキテクチャから 1 つの ProductRow
インスタンスのみを取得することです。
ProductRow
のプロパティを割り当てるコードは、全体的なパターンとして、UpdateProducts
メソッドと For Each
の BatchUpdate
ループ内のコードは同じです。
このチュートリアルを完了するには、いずれかの [Update Products] ボタンがクリックされたときに BatchUpdate
メソッドを呼び出す必要があります。 これら 2 つの Button コントロールの Click
イベントに対するイベント ハンドラーを作成し、そのイベント ハンドラーに次のコードを追加します。
BatchUpdate()
ClientScript.RegisterStartupScript(Me.GetType(), "message", _
"alert('The products have been updated.');", True)
まず、BatchUpdate
を呼び出します。 次に、 ClientScript
プロパティ を使用して 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 の メソッドから "すべて" の製品を取得し、その後 GridView に表示されるレコードのみを更新します。GetProducts
この方法は、GridView でページングを使わない場合には最適ですが、ページングを使う場合は、数百、数千、または数万もの製品が存在するのに、GridView には 10 行しか含まれない可能性があります。 このような場合、データベースからすべての製品を取得して、そのうちの 10 個しか変更しないのは理想的とはいえません。
このような状況では、代わりに次の BatchUpdateAlternate
メソッドを使うことを検討してください。
Private Sub BatchUpdateAlternate()
' Enumerate the GridView's Rows collection and create a ProductRow
Dim productsAPI As New ProductsBLL()
Dim products As New Northwind.ProductsDataTable()
For Each gvRow As GridViewRow In ProductsGrid.Rows
' Create a new ProductRow instance
Dim productID As Integer = _
Convert.ToInt32(ProductsGrid.DataKeys(gvRow.RowIndex).Value)
Dim currentProductDataTable As Northwind.ProductsDataTable = _
productsAPI.GetProductByProductID(productID)
If currentProductDataTable.Rows.Count > 0 Then
Dim product As Northwind.ProductsRow = currentProductDataTable(0)
Dim productName As TextBox = _
CType(gvRow.FindControl("ProductName"), TextBox)
Dim categories As DropDownList = _
CType(gvRow.FindControl("Categories"), DropDownList)
Dim unitPrice As TextBox = _
CType(gvRow.FindControl("UnitPrice"), TextBox)
Dim discontinued As CheckBox = _
CType(gvRow.FindControl("Discontinued"), CheckBox)
' Assign the user-entered values to the current ProductRow
product.ProductName = productName.Text.Trim()
If categories.SelectedIndex = 0 Then
product.SetCategoryIDNull()
Else
product.CategoryID = Convert.ToInt32(categories.SelectedValue)
End If
If unitPrice.Text.Trim().Length = 0 Then
product.SetUnitPriceNull()
Else
product.UnitPrice = Convert.ToDecimal(unitPrice.Text)
End If
product.Discontinued = discontinued.Checked
' Import the ProductRow into the products DataTable
products.ImportRow(product)
End If
Next
' Now have the BLL update the products data using a transaction
productsAPI.UpdateProductsWithTransaction(products)
End Sub
BatchMethodAlternate
では最初に、ProductsDataTable
という名前の新しい空の products
を作成します。 次に、GridView の Rows
コレクションをステップ実行し、行ごとに BLL の GetProductByProductID(productID)
メソッドを使って特定の製品情報を取得します。 取得したProductsRow
インスタンスのプロパティは、BatchUpdate
と同じ方法で更新されますが、行を更新した後、DataTable の ProductsDataTable
を使用してImportRow(DataRow)
にインポートされます。
For Each
ループが完了すると、products
には GridView の行ごとに 1 つの ProductsRow
インスタンスが含まれています。 各 ProductsRow
インスタンスは (更新されるのではなく) products
に追加されているため、これを無条件で UpdateWithTransaction
メソッドに渡すと、ProductsTableAdapter
は各レコードをデータベースに挿入しようと試みます。 代わりに、これらの各行が (追加ではなく) 変更されたことを指定する必要があります。
これを実現するために、UpdateProductsWithTransaction
という名前の新しいメソッドを BLL に追加します。 以下に示す UpdateProductsWithTransaction
では、RowState
内の各 ProductsRow
インスタンスの ProductsDataTable
を Modified
に設定してから、ProductsDataTable
を DAL の UpdateWithTransaction
メソッドに渡しています。
Public Function UpdateProductsWithTransaction _
(ByVal products As Northwind.ProductsDataTable) As Integer
' Mark each product as Modified
products.AcceptChanges()
For Each product As Northwind.ProductsRow In products
product.SetModified()
Next
' Update the data via a transaction
Return UpdateWithTransaction(products)
End Function
まとめ
GridView には、行ごとの編集機能が組み込みで用意されていますが、完全に編集可能なインターフェイスの作成はサポートされていません。 このチュートリアルで説明したように、このようなインターフェイスは実現可能ですが、少し作業が必要になります。 すべての行が編集可能な GridView を作成するには、GridView のフィールドを TemplateField に変換し、ItemTemplate
内で編集インターフェイスを定義する必要があります。 さらに、[すべて更新] タイプの Button Web コントロールは、GridView とは別にページに追加する必要があります。 こうした Button の Click
イベント ハンドラーでは、GridView の Rows
コレクションを列挙し、変更を ProductsDataTable
に格納して、更新された情報を適切な BLL メソッドに渡す必要があります。
次のチュートリアルでは、バッチ削除用のインターフェイスを作成する方法について説明します。 具体的には、GridView の各行にチェックボックスを追加し、[すべて更新] タイプのボタンの代わりに、[Delete Selected Rows] (選択した行の削除) ボタンを設定します。
プログラミングに満足!
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを使用しています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・セルフ ASP.NET 24時間で2.0です。 彼には mitchell@4GuysFromRolla.comで連絡できます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Teresa Murphy と David Suru でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、mitchell@4GuysFromRolla.comにご連絡ください。