一括挿入 (C#)
1 回の操作で複数のデータベース レコードを挿入する方法について説明します。 ユーザー インターフェイス レイヤーでは、GridView を拡張して、ユーザーが複数の新しいレコードを入力できるようにします。 データ アクセス層では、トランザクション内で複数の挿入操作をラップすることで、すべての挿入が成功するか、すべての挿入がロールバックされるようにします。
はじめに
「一括更新」チュートリアルでは、GridView コントロールをカスタマイズして、複数のレコードが編集可能なインターフェイスを表示する方法について説明しました。 ページにアクセスするユーザーは一連の変更を行い、その後、1 回のボタン クリックでバッチ更新を実行できます。 ユーザーが通常一度に多くのレコードを更新する場合、このようなインターフェイスは、「データの挿入、更新、削除の概要データの挿入、更新、および削除の概要」チュートリアルで最初に確認した既定の行ごとの編集機能と比べ、多数のクリックとキーボードからマウスへのコンテキストの切り替えを保存することになる可能性があります。
この概念は、レコードを追加するときにも当てはまります。 ここでは、特定のカテゴリの製品を多数含むサプライヤーからの出荷を頻繁に受け入れるノースウィンド貿易会社のことを想像してみてください。 たとえば、東京貿易会社から出荷される 6 種類の紅茶製品とコーヒー製品を受け入れるとします。 ユーザーが DetailsView コントロールを使用して 6 つの製品を一度に 1 件ずつ入力する場合、同じ値を何度も繰り返し選択する必要があります。同じカテゴリ (飲料)、同じサプライヤー (東京貿易会社)、同じ生産中止値 (False)、注文時の単位値 (0) を選択することになります。 この繰り返しのデータ入力には時間がかかるだけでなく、エラーが発生しやすくなります。
少しの作業で、ユーザーがサプライヤーとカテゴリを一回選択し、一連の製品名と単価を入力し、ボタンをクリックして新しい製品をデータベースに追加できるようにする一括挿入インターフェイスを作成できます (図 1 を参照)。 各製品が追加されると、その ProductName
と UnitPrice
のデータ フィールドには TextBoxes に入力された値が割り当てられます。一方で、その CategoryID
値と SupplierID
値には、フォームの上部にある DropDownLists の値が割り当てられます。 Discontinued
値と UnitsOnOrder
値はそれぞれ false
と 0 のハードコーディングされた値に設定されます。
図 1: 一括挿入インターフェイス (フルサイズの画像を表示するにはクリック)
このチュートリアルでは、図 1 に示す一括挿入インターフェイスを実装するページを作成します。 前の 2 つのチュートリアルと同様に、アトミック性を確保するために、トランザクションのスコープ内で挿入をラップします。 では、始めましょう。
手順 1: 表示インターフェイスの作成
このチュートリアルは、表示領域と挿入領域の 2 つの領域に分割された 1 つのページで構成されます。 この手順で作成する表示インターフェイスには、GridView に製品が表示され、「Process Product Shipment」というタイトルのボタンが含まれています。 このボタンをクリックすると、図 1 に示すように、表示インターフェイスが挿入インターフェイスに置き換えられます。 [Add Products from Shipment] または [Cancel] ボタンがクリックされた後は、表示インターフェイスに戻ります。 手順 2 で挿入インターフェイスを作成します。
一度に 1 つだけ表示される 2 つのインターフェイスを持つページを作成する場合、通常各インターフェイスは、他のコントロールのコンテナーとして機能する Panel Web コントロール内に配置されます。 したがって、このページには、各インターフェイスに 1 つずつの 2 つの Panel コントロールがあります。
まず、BatchData
フォルダーの BatchInsert.aspx
ページを開き、ツールボックスからデザイナーにパネルをドラッグします (図 2 を参照)。 パネルの ID
プロパティを DisplayInterface
に設定します。 パネルをデザイナーに追加すると、その Height
プロパティと Width
プロパティはそれぞれ 50px と 125px に設定されます。 [プロパティ] ウィンドウからこれらのプロパティ値をクリアします。
図 2: ツールボックスからデザイナーにパネルをドラッグする (フルサイズの画像を表示するにはクリック)
次に、Button コントロールと GridView コントロールを Panel にドラッグします。 ボタンの ID
プロパティを ProcessShipment
に設定し、その Text
プロパティを「Process Product Shipment」に設定します。 GridView の ID
プロパティを ProductsGrid
に設定し、スマート タグから ProductsDataSource
という名前の新しい ObjectDataSource にバインドします。 ProductsBLL
クラスの GetProducts
メソッドからデータをプルするように ObjectDataSource を構成します。 この GridView はデータの表示にのみ使用されるため、[UPDATE]、[INSERT]、[DELETE] タブのドロップダウン リストを [(None)] に設定します。 [完了] をクリックして、データ ソースの構成ウィザードを完了します。
図 3: ProductsBLL
クラスの GetProducts
メソッドから返されたデータを表示する (フルサイズの画像を表示するにはクリック)
図 4: [UPDATE]、[INSERT]、[DELETE] の各タブのドロップダウン リストを [(None)] に設定する (フルサイズの画像を表示するにはクリック)
ObjectDataSource ウィザードが完了すると、Visual Studio によって、製品データ フィールドの BoundFields と CheckBoxField が追加されます。 ProductName
、CategoryName
、SupplierName
、UnitPrice
、Discontinued
の各フィールドを除くすべてのフィールドを削除します。 外観は自由にカスタマイズできます。 UnitPrice
フィールドを通貨値として書式設定し、フィールドを並べ替え、いくつかの HeaderText
フィールドの名前の値を変更しました。 また、GridView のスマート タグで [ページングを有効にする] チェック ボックスと [並べ替えを有効にする] チェック ボックスをオンにして、ページングと並べ替えのサポートを含むように GridView を構成します。
Panel、Button、GridView、ObjectDataSource の各コントロールを追加し、GridView のフィールドをカスタマイズすると、ページの宣言型マークアップは次のようになります。
<asp:Panel ID="DisplayInterface" runat="server">
<p>
<asp:Button ID="ProcessShipment" runat="server"
Text="Process Product Shipment" />
</p>
<asp:GridView ID="ProductsGrid" runat="server" AllowPaging="True"
AllowSorting="True" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsDataSource">
<Columns>
<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="UnitPrice" DataFormatString="{0:c}"
HeaderText="Price" HtmlEncode="False"
SortExpression="UnitPrice">
<ItemStyle HorizontalAlign="Right" />
</asp:BoundField>
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued">
<ItemStyle HorizontalAlign="Center" />
</asp:CheckBoxField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>
</asp:Panel>
ボタンと GridView のマークアップは、開始タグと <asp:Panel>
終了タグの内側に配置されることに注意してください。 これらのコントロールは DisplayInterface
Panel 内に存在するため、Panel の Visible
プロパティを false
に設定するだけで非表示にできます。 手順 3 では、ボタンのクリックに応じて一方のインターフェイスを表示し、もう一方のインターフェイスを非表示にする Panel の Visible
プロパティのプログラムによる変更を示しています。
ブラウザーを使用して進行状況を確認します。 図 5 に示すように、一度に 10 個の製品を一覧表示する GridView の上に [Process Product Shipment] ボタンが表示されます。
図 5: GridView は製品を一覧表示し、並べ替えとページング機能を提供する (フルサイズの画像を表示するにはクリック)
手順 2: 挿入インターフェイスの作成
表示インターフェイスが完成したら、挿入インターフェイスを作成する準備ができました。 このチュートリアルでは、1 つのサプライヤーとカテゴリの値の入力を求める挿入インターフェイスを作成し、ユーザーが最大 5 つの製品名と単価の値を入力できるようにします。 このインターフェイスを使用すると、ユーザーは、すべて同じカテゴリとサプライヤーを共有し、固有の製品名と価格を持つ 1 個から 5 個の新製品を追加できます。
まず、ツールボックスからデザイナーにパネルをドラッグし、既存の DisplayInterface
パネルの下に配置します。 この新しく追加されたパネルの ID
プロパティを InsertingInterface
に設定し、その Visible
プロパティを false
に設定します。 手順 3 で、InsertingInterface
Panel の Visible
プロパティを true
に設定するコードを追加します。 また、Panel の Height
プロパティの値と Width
プロパティの値をクリアします。
次に、前の図 1 に示した挿入インターフェイスを作成する必要があります。 このインターフェイスは、さまざまな HTML 手法を使用して作成できますが、ここでは 4 列 7 行のテーブルという非常に単純なインターフェイスを使用します。
Note
HTML <table>
要素のマークアップを入力するときは、ソース ビューを使用するのをお勧めします。 Visual Studio には、デザイナーを使用して <table>
要素を追加するためのツールが含まれていますが、このデザイナーでは、マークアップに指定していない style
設定が挿入されがちです。 <table>
マークアップを作成したら、通常は、デザイナーに戻って Web コントロールを追加し、それらのプロパティを設定するのが適しています。 事前に決定された列と行を含むテーブルを作成する場合、Table Web コントロールでなく、静的な HTML を使用することをお勧めします。これは、Table Web コントロール内に配置された Web コントロールには、FindControl("controlID")
パターンを使用してのみアクセスできるためです。 しかし、動的にサイズ変更されるテーブル (行または列がデータベースまたはユーザー指定の条件に基づくもの) の場合、Table Web コントロールはプログラムによって構築できるため、Table Web コントロールを使用することをお勧めします。
InsertingInterface
Panel の <asp:Panel>
タグ内に、次のマークアップを入力します。
<table class="DataWebControlStyle" cellspacing="0">
<tr class="BatchInsertHeaderRow">
<td class="BatchInsertLabel">Supplier:</td>
<td></td>
<td class="BatchInsertLabel">Category:</td>
<td></td>
</tr>
<tr class="BatchInsertRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertAlternatingRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertAlternatingRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertRow">
<td class="BatchInsertLabel">Product:</td>
<td></td>
<td class="BatchInsertLabel">Price:</td>
<td></td>
</tr>
<tr class="BatchInsertFooterRow">
<td colspan="4">
</td>
</tr>
</table>
この <table>
マークアップには、Web コントロールはまだ含まれていません。これらのコントロールをこれから追加します。 各 <tr>
要素に、特定の CSS クラス設定 BatchInsertHeaderRow
が、サプライヤーとカテゴリの DropDownList が配置されるヘッダー行のために含まれており、BatchInsertFooterRow
が、[Add Products from Shipment] ボタンと [Cancel] ボタンが配置されるフッター行のために含まれていることに注目してください。また、製品と単価の TextBox コントロールを含む行のために、BatchInsertRow
値と BatchInsertAlternatingRow
値が交互に含められています。 これらのチュートリアルで使用した GridView コントロールと DetailsView コントロールのような外観を挿入インターフェイスに与えるために、Styles.css
ファイルに対応する CSS クラスを作成しました。 これらの CSS クラスを次に示します。
/*** Styles for ~/BatchData/BatchInsert.aspx tutorial ***/
.BatchInsertLabel
{
font-weight: bold;
text-align: right;
}
.BatchInsertHeaderRow td
{
color: White;
background-color: #900;
padding: 11px;
}
.BatchInsertFooterRow td
{
text-align: center;
padding-top: 5px;
}
.BatchInsertRow
{
}
.BatchInsertAlternatingRow
{
background-color: #fcc;
}
このマークアップを入力して、デザイン ビューに戻ります。 図 6 に示すように、この <table>
は、デザイナーに 4 列 7 行のテーブルとして表示されます。
図 6: 挿入インターフェイスは、4 列 7 行のテーブルで構成されている (フルサイズの画像を表示するにはクリック)
これで、挿入インターフェイスに Web コントロールを追加する準備ができました。 ツールボックスから 2 つの DropDownList を、サプライヤー用とカテゴリ用のテーブルの適切なセルにドラッグします。
サプライヤー DropDownList の ID
プロパティを Suppliers
に設定し、これを SuppliersDataSource
という名前の新しい ObjectDataSource にバインドします。 新しい ObjectDataSource を構成して、SuppliersBLL
クラスの GetSuppliers
メソッドからデータを取得し、UPDATE タブのドロップダウン リストを [(None)] に設定します。 [完了] をクリックして、ウィザードを完了します。
図 7: SuppliersBLL
クラスの GetSuppliers
メソッドを使用するように ObjectDataSource を構成する (フルサイズの画像を表示するにはクリック)
Suppliers
DropDownList に CompanyName
データ フィールドを表示し、SupplierID
データ フィールドを ListItem
の値として使用します。
図 8: CompanyName
データ フィールドを表示し、値として SupplierID
を使用する (フルサイズの画像を表示するにはクリック)
2 番目の DropDownList に Categories
という名前を付け、CategoriesDataSource
という名前の新しい ObjectDataSource にバインドします。 CategoriesDataSource
ObjectDataSource が CategoriesBLL
クラスの GetCategories
メソッドを使用するように構成します。[UPDATE] タブと [DELETE] タブのドロップダウン リストを [(None)] に設定し、[完了] をクリックしてウィザードを完了します。 最後に、DropDownList に CategoryName
データ フィールドを表示させ、CategoryID
を値として使用します。
これら 2 つの DropDownList が追加され、適切に構成された ObjectDataSource にバインドされると、画面は図 9 のようになります。
図 9: ヘッダー行に Suppliers
と Categories
DropDownLists が含まれるようになりました (フルサイズの画像を表示するにはクリック)
ここでは、新しい各製品の名前と価格を収集する TextBoxes を作成する必要があります。 ツールボックスから 5 つの製品名と価格行のそれぞれについて、TextBox コントロールをデザイナーにドラッグします。 TextBoxes の ID
プロパティを、ProductName1
、UnitPrice1
、ProductName2
、UnitPrice2
、ProductName3
、UnitPrice3
のように設定します。
各単価の TextBoxes の後に CompareValidator を追加し、ControlToValidate
プロパティを適切な ID
に設定します。 また、Operator
プロパティを GreaterThanEqual
に設定し、ValueToCompare
を 0 に設定し、Type
を Currency
に設定します。 これらの設定は、CompareValidator に対して、入力された価格が 0 以上の有効な通貨値であることを確認するように指示します。 Text
プロパティを * に設定し、ErrorMessage
を 0 以上の価格に設定する必要があります。 また、通貨記号は省略してください。
Note
Products
データベース テーブルの ProductName
フィールドで NULL
値が許可されていない場合でも、挿入インターフェイスには RequiredFieldValidator コントロールは含まれません。 これは、ユーザーが最大 5 つの製品を入力できるようにするためです。 たとえば、ユーザーが最初の 3 行の製品名と単価を指定し、最後の 2 行を空白のままにした場合、3 つの新しい製品をシステムに追加するだけです。 ただし、ProductName
が必須であるため、単価が入力され、対応する製品名の値が提供されることをプログラムによって確認する必要があります。 手順 4 でこのチェックに取り組みます。
ユーザーの入力を検証するときに、値に通貨記号が含まれている場合、CompareValidator は無効なデータを報告します。 各単価の TextBoxes の前に $ を追加し、ユーザーが価格を入力するときに、通貨記号を省略することが視覚的にわかる手掛かりとして機能させます。
最後に、InsertingInterface
Panel 内に ValidationSummary コントロールを追加し、ShowMessageBox
プロパティを true
に設定し、その ShowSummary
プロパティを false
に設定します。 これらの設定では、ユーザーが無効な単価の値を入力すると、問題のある TextBox コントロールの横にアスタリスクが表示され、ValidationSummary に、前に指定したエラー メッセージを示すクライアント側のメッセージ ボックスが表示されます。
この時点で、画面は図 10 のようになります。
図 10: 挿入インターフェイスに、製品名と価格の TextBoxes が含まれるようになりました (フルサイズの画像を表示するにはクリック)
次に、[Add Products from Shipment] ボタンと [Cancel] ボタンをフッター行に追加する必要があります。 ツールボックスから挿入インターフェイスのフッターに 2 つの Button コントロールをドラッグし、ボタンの ID
プロパティを AddProducts
に設定し、CancelButton
プロパティと Text
プロパティを [Add Products from Shipment] と [Cancel] にそれぞれ設定します。 さらに、CancelButton
コントロールの CausesValidation
プロパティを false
に設定します。
最後に、2 つのインターフェイスのステータス メッセージを表示する Label Web コントロールを追加する必要があります。 たとえば、ユーザーが製品の新しい出荷を正常に追加したら、表示インターフェイスに戻り、確認メッセージを表示します。 ただし、ユーザーが新しい製品の価格を提供しても製品名を省略した場合は、ProductName
フィールドが必須であるため、警告メッセージを表示する必要があります。 このメッセージは両方のインターフェイスに対して表示する必要があるため、Panels の外側のページの上部に配置します。
ラベル Web コントロールをツールボックスからデザイナーのページの上部にドラッグします。 ID
プロパティを StatusLabel
に設定し、Text
プロパティをクリアし、Visible
プロパティと EnableViewState
プロパティを false
に設定します。 前のチュートリアルで説明したように、EnableViewState
プロパティを false
に設定すると、ラベルのプロパティ値をプログラムによって変更し、後続のポストバックで自動的に既定値に戻すことができます。 これにより、後続のポストバックで消えるユーザー アクションに応答してステータス メッセージを表示するコードが簡略化されます。 最後に、StatusLabel
コントロールの CssClass
プロパティを [Warning] に設定します。これは、大きな斜体、太字、赤のフォントでテキストを表示する Styles.css
で定義された CSS クラスの名前です。
図 11 は、ラベルが追加および構成された後の Visual Studio デザイナーを示しています。
図 11: StatusLabel
コントロールを 2 つのパネル コントロールの上に配置する (フルサイズの画像を表示するにはクリック)
手順 3: 表示インターフェイスと挿入インターフェイスの切り替え
この時点で、表示インターフェイスと挿入インターフェイスのマークアップを完了しましたが、まだ次の 2 つのタスクが残っています。
- 表示インターフェイスと挿入インターフェイスの切り替え
- 出荷の製品のデータベースへの追加
現在、表示インターフェイスは表示されますが、挿入インターフェイスは非表示になっています。 これは、DisplayInterface
Panel の Visible
プロパティが true
(既定値) に設定されているのに対し、InsertingInterface
Panel の Visible
プロパティは false
に設定されているためです。 2 つのインターフェイスを切り替えるには、各コントロールの Visible
プロパティ値を切り替える必要があります。
[Process Product Shipment] ボタンがクリックされると、表示インタフェースから挿入インタフェースに移動します。 したがって、次のコードを含むこのボタンの Click
イベントのイベント ハンドラーを作成します。
protected void ProcessShipment_Click(object sender, EventArgs e)
{
DisplayInterface.Visible = false;
InsertingInterface.Visible = true;
}
このコードでは、DisplayInterface
Panel が非表示になり、InsertingInterface
Panel が表示されます。
次に、挿入インターフェイスの [Add Products from Shipment] ボタン コントロールと [Cancel] ボタン コントロールのイベント ハンドラーを作成します。 これらのボタンのいずれかがクリックされたら、表示インターフェイスに戻す必要があります。 両方の Button コントロールの Click
イベント ハンドラーを作成して、そられが ReturnToDisplayInterface
を呼び出せるようにします。これは、これから追加するメソッドです。 ReturnToDisplayInterface
メソッドは、InsertingInterface
Panel を非表示にし、DisplayInterface
Panel を表示するだけでなく、Web コントロールを編集前の状態に戻す必要があります。 これには、DropDownLists SelectedIndex
プロパティを 0 に設定し、TextBox コントロールの Text
プロパティをクリアする必要があります。
Note
表示インターフェイスに戻る前に、コントロールを編集前の状態に戻さなかった場合に何が起こるかを考えてください。 ユーザーは、[Process Product Shipment] ボタンをクリックし、出荷の製品を入力して、[Add Products from Shipment] をクリックします。 これにより、製品が追加され、ユーザーは表示インターフェイスに戻ります。 この時点で、ユーザーは別の出荷を追加したくなる場合があります。 [Process Product Shipment] ボタンをクリックすると、挿入インターフェイスに戻りますが、DropDownList の選択と TextBox の値には以前の値が設定されたままです。
protected void AddProducts_Click(object sender, EventArgs e)
{
// TODO: Save the products
// Revert to the display interface
ReturnToDisplayInterface();
}
protected void CancelButton_Click(object sender, EventArgs e)
{
// Revert to the display interface
ReturnToDisplayInterface();
}
const int firstControlID = 1;
const int lastControlID = 5;
private void ReturnToDisplayInterface()
{
// Reset the control values in the inserting interface
Suppliers.SelectedIndex = 0;
Categories.SelectedIndex = 0;
for (int i = firstControlID; i <= lastControlID; i++)
{
((TextBox)InsertingInterface.FindControl("ProductName" + i.ToString())).Text =
string.Empty;
((TextBox)InsertingInterface.FindControl("UnitPrice" + i.ToString())).Text =
string.Empty;
}
DisplayInterface.Visible = true;
InsertingInterface.Visible = false;
}
どちらの Click
イベント ハンドラーも、単に ReturnToDisplayInterface
メソッドを呼び出しますが、手順 4 で [Add Products from Shipment] の Click
イベント ハンドラーに戻り、製品を保存するコードを追加します。 ReturnToDisplayInterface
は、最初のオプションに Suppliers
および Categories
DropDownLists を返すことから開始します。 2 つの定数 firstControlID
および lastControlID
は、挿入インターフェイスで製品名と単価の TextBoxes の名前付けに使用される開始および終了コントロールのインデックス値をマークし、TextBox コントロールの Text
プロパティを空の文字列に戻す for
ループの境界で使用されます。 最後に、挿入インターフェイスが非表示になり、表示インターフェイスが表示されるように、Panel の Visible
プロパティがリセットされます。
ブラウザーでこのページをテストします。 最初にページにアクセスすると、図 5 に示すように表示インターフェイスが表示されます。 [Process Product Shipment] ボタンをクリックします。 ページがポストバックされ、図 12 に示すように挿入インターフェイスが表示されます。 [Add Products from Shipment] ボタンまたは [Cancel] ボタンをクリックすると、表示インターフェイスに戻ります。
Note
挿入インターフェイスを表示しているときに、単価 TextBoxes で CompareValidators をテストします。 無効な通貨値または値が 0 未満の価格の [Add Products from Shipment] ボタンをクリックすると、クライアント側のメッセージ ボックスの警告が表示されます。
図 12: [Process Product Shipment] ボタンをクリックした後、挿入インターフェイスが表示される (フルサイズの画像を表示するにはクリック)
手順 4: 製品の追加
このチュートリアルの残りの手順は、[Add Products from Shipment] ボタンの Click
イベント ハンドラーでデータベースに保存することです。 これを実現するには、ProductsDataTable
を作成し、指定された製品名ごとに ProductsRow
インスタンスを追加します。 これらの ProductsRow
が追加されたら、ProductsDataTable
で渡す ProductsBLL
クラスの UpdateWithTransaction
メソッドを呼び出します。 「トランザクション内のデータベース変更のラップ」チュートリアルで作成された UpdateWithTransaction
メソッドは、ProductsDataTable
を ProductsTableAdapter
の UpdateWithTransaction
メソッドに渡すことを思い出してください。 そこから、ADO.NET トランザクションが開始され、TableAdapter は、DataTable に追加された各 ProductsRow
に対して INSERT
ステートメントをデータベースに発行します。 すべての製品がエラーなしで追加されると仮定すると、トランザクションはコミットされ、それ以外の場合はロールバックされます。
[Add Products from Shipment] ボタンの Click
イベント ハンドラーのためのコードも、少しエラー チェックを実行する必要があります。 挿入インターフェイスで使用される RequiredFieldValidators がないため、ユーザーは名前を省略しつつ、製品の価格を入力する可能性があります。 製品名は必須であるため、このような条件が明らかになった場合は、ユーザーに警告し、挿入を続行しないようにする必要があります。 Click
イベント ハンドラーの全コードは次のとおりです。
protected void AddProducts_Click(object sender, EventArgs e)
{
// Make sure that the UnitPrice CompareValidators report valid data...
if (!Page.IsValid)
return;
// Add new ProductsRows to a ProductsDataTable...
Northwind.ProductsDataTable products = new Northwind.ProductsDataTable();
for (int i = firstControlID; i <= lastControlID; i++)
{
// Read in the values for the product name and unit price
string productName = ((TextBox)InsertingInterface.FindControl
("ProductName" + i.ToString())).Text.Trim();
string unitPrice = ((TextBox)InsertingInterface.FindControl
("UnitPrice" + i.ToString())).Text.Trim();
// Ensure that if unitPrice has a value, so does productName
if (unitPrice.Length > 0 && productName.Length == 0)
{
// Display a warning and exit this event handler
StatusLabel.Text = "If you provide a unit price you must also " +
"include the name of the product.";
StatusLabel.Visible = true;
return;
}
// Only add the product if a product name value is provided
if (productName.Length > 0)
{
// Add a new ProductsRow to the ProductsDataTable
Northwind.ProductsRow newProduct = products.NewProductsRow();
// Assign the values from the web page
newProduct.ProductName = productName;
newProduct.SupplierID = Convert.ToInt32(Suppliers.SelectedValue);
newProduct.CategoryID = Convert.ToInt32(Categories.SelectedValue);
if (unitPrice.Length > 0)
newProduct.UnitPrice = Convert.ToDecimal(unitPrice);
// Add any "default" values
newProduct.Discontinued = false;
newProduct.UnitsOnOrder = 0;
products.AddProductsRow(newProduct);
}
}
// If we reach here, see if there were any products added
if (products.Count > 0)
{
// Add the new products to the database using a transaction
ProductsBLL productsAPI = new ProductsBLL();
productsAPI.UpdateWithTransaction(products);
// Rebind the data to the grid so that the products just added are displayed
ProductsGrid.DataBind();
// Display a confirmation (don't use the Warning CSS class, though)
StatusLabel.CssClass = string.Empty;
StatusLabel.Text = string.Format(
"{0} products from supplier {1} have been added and filed under " +
"category {2}.", products.Count, Suppliers.SelectedItem.Text,
Categories.SelectedItem.Text);
StatusLabel.Visible = true;
// Revert to the display interface
ReturnToDisplayInterface();
}
else
{
// No products supplied!
StatusLabel.Text = "No products were added. Please enter the product " +
"names and unit prices in the textboxes.";
StatusLabel.Visible = true;
}
}
このイベント ハンドラーは、Page.IsValid
プロパティが true
値を返すのを確認することから開始します。 false
を返す場合は、1 つ以上の CompareValidators が無効なデータを報告していることを意味します。このような場合は、入力された製品を挿入しないようにする必要があります。そうしないと、ユーザーが入力した単価の値を ProductsRow
の UnitPrice
プロパティに割り当てようとすると、例外が発生します。
次に、新しい ProductsDataTable
インスタンスが作成されます (products
)。 for
ループを使用して製品名と単価の TextBoxes を反復処理し、Text
プロパティが、ローカル変数 productName
と unitPrice
に読み込まれます。 ユーザーが単価の値を入力し、対応する製品名の値を入力していない場合は、StatusLabel
にメッセージが表示されます。単価を指定する場合は、製品の名前も含める必要があり、イベント ハンドラーが終了します。
製品名が指定されている場合は、ProductsDataTable
の NewProductsRow
メソッドを使用して新しい ProductsRow
インスタンスが作成されます。 この新しい ProductsRow
インスタンスの ProductName
プロパティは、現在の製品名 TextBox に設定され、一方 SupplierID
プロパティと CategoryID
プロパティは、挿入インターフェイスのヘッダーの DropDownLists の SelectedValue
プロパティに割り当てられます。 ユーザーが製品の価格の値を入力した場合は、ProductsRow
インスタンスの UnitPrice
プロパティに割り当てられます。それ以外の場合、プロパティは割り当てられないままになり、データベース内の UnitPrice
の NULL
値になります。 最後に、Discontinued
プロパティと UnitsOnOrder
プロパティは、それぞれハードコーディングされた値 false
と 0 に割り当てられます。
プロパティが ProductsRow
インスタンスに割り当てられた後、ProductsDataTable
に追加されます。
for
ループが完了すると、製品が追加されたかどうかを確認します。 ユーザーは、結局のところ、製品名や価格を入力する前に、[Add Products from Shipment] をクリックすると考えられます。 ProductsDataTable
に少なくとも 1 つの製品がある場合は、ProductsBLL
クラスの UpdateWithTransaction
メソッドが呼び出されます。 次に、新しく追加された製品が表示インターフェイスに表示されるように、データは ProductsGrid
GridView に再バインドされます。 StatusLabel
が更新されて確認メッセージが表示され、ReturnToDisplayInterface
が呼び出され、挿入インターフェイスが非表示になり、表示インターフェイスが表示されます。
製品が入力されなかった場合、挿入インターフェイスは表示されたままですが、「No products were added. Please enter the product names and unit prices in the textboxes」というメッセージが表示されます。
図 13、14、15 は、実際のインターフェイスの挿入と表示を示しています。 図 13 では、ユーザーは対応する製品名なしで単価の値を入力しています。 図 14 は、3 つの新しい製品が正常に追加された後の表示インターフェイスを示しています。図 15 は、GridView に新しく追加された 2 つの製品を示しています (3 番目の製品は前のページにあります)。
図 13: 単価を入力するときに製品名が要求される (フルサイズの画像を表示するにはクリック)
図 14: サプライヤー Mayumi に 3 つの新しい野菜が追加された (フルサイズの画像を表示するにはクリック)
図 15: 新しい製品は、GridView の最後のページで見つけられる (フルサイズの画像を表示するにはクリック)
Note
このチュートリアルで使用する一括挿入ロジックは、トランザクションのスコープ内で挿入をラップします。 これを確認するには、意図的にデータベース レベルのエラーを発生させます。 たとえば、新しい ProductsRow
インスタンスの CategoryID
プロパティは、Categories
DropDownList で選択した値に割り当てるのでなく、i * 5
などの値に割り当てます。 ここで i
はループ インデクサーであり、1 から 5 の範囲の値を持ちます。 したがって、2 つ以上の製品を一括挿入で追加すると、最初の製品は有効な CategoryID
値 (5) になりますが、後続の製品は、Categories
テーブルの最大 CategoryID
個の値と一致しない CategoryID
値になります。 その結果、最初の INSERT
は成功しますが、後続のは外部キー制約違反で失敗します。 一括挿入はアトミックであるため、最初の INSERT
はロールバックされ、データベースは、一括挿入プロセスが開始される前の状態に戻されます。
まとめ
このチュートリアルと前の 2 つのチュートリアルでは、データのバッチの更新、削除、挿入を可能にするインターフェイスを作成しました。それらのすべてが、「トランザクション内のデータベース変更をラップする」チュートリアル内のデータ アクセス層に追加したトランザクション サポートを使用しました。 特定のシナリオでは、このようなバッチ処理ユーザー インターフェイスは、基になるデータの整合性を維持しながら、クリック数、ポストバック、キーボードからマウスへのコンテキスト切り替えの数を減らすことで、エンド ユーザーの効率を大幅に向上させます。
このチュートリアルでは、バッチ処理されるデータの操作に関する説明を完了しました。 次の一連のチュートリアルでは、TableAdapter メソッドでのストアド プロシージャの使用、DAL での接続レベルとコマンド レベルの設定の構成、接続文字列の暗号化など、さまざまな高度なデータ アクセス層シナリオについて説明します。
プログラミングに満足!
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを扱っています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Hilton Giesenow と S ren Jacob Lauritsen でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。