次の方法で共有


一括挿入 (C#)

作成者: Scott Mitchell

PDF のダウンロード

1 回の操作で複数のデータベース レコードを挿入する方法について説明します。 ユーザー インターフェイス レイヤーでは、GridView を拡張して、ユーザーが複数の新しいレコードを入力できるようにします。 データ アクセス層では、トランザクション内で複数の挿入操作をラップすることで、すべての挿入が成功するか、すべての挿入がロールバックされるようにします。

はじめに

一括更新」チュートリアルでは、GridView コントロールをカスタマイズして、複数のレコードが編集可能なインターフェイスを表示する方法について説明しました。 ページにアクセスするユーザーは一連の変更を行い、その後、1 回のボタン クリックでバッチ更新を実行できます。 ユーザーが通常一度に多くのレコードを更新する場合、このようなインターフェイスは、「データの挿入、更新、削除の概要データの挿入、更新、および削除の概要」チュートリアルで最初に確認した既定の行ごとの編集機能と比べ、多数のクリックとキーボードからマウスへのコンテキストの切り替えを保存することになる可能性があります。

この概念は、レコードを追加するときにも当てはまります。 ここでは、特定のカテゴリの製品を多数含むサプライヤーからの出荷を頻繁に受け入れるノースウィンド貿易会社のことを想像してみてください。 たとえば、東京貿易会社から出荷される 6 種類の紅茶製品とコーヒー製品を受け入れるとします。 ユーザーが DetailsView コントロールを使用して 6 つの製品を一度に 1 件ずつ入力する場合、同じ値を何度も繰り返し選択する必要があります。同じカテゴリ (飲料)、同じサプライヤー (東京貿易会社)、同じ生産中止値 (False)、注文時の単位値 (0) を選択することになります。 この繰り返しのデータ入力には時間がかかるだけでなく、エラーが発生しやすくなります。

少しの作業で、ユーザーがサプライヤーとカテゴリを一回選択し、一連の製品名と単価を入力し、ボタンをクリックして新しい製品をデータベースに追加できるようにする一括挿入インターフェイスを作成できます (図 1 を参照)。 各製品が追加されると、その ProductNameUnitPrice のデータ フィールドには TextBoxes に入力された値が割り当てられます。一方で、その CategoryID 値と SupplierID 値には、フォームの上部にある DropDownLists の値が割り当てられます。 Discontinued 値と UnitsOnOrder 値はそれぞれ false と 0 のハードコーディングされた値に設定されます。

The Batch Inserting Interface

図 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 に設定されます。 [プロパティ] ウィンドウからこれらのプロパティ値をクリアします。

Drag a Panel from the Toolbox onto the Designer

図 2: ツールボックスからデザイナーにパネルをドラッグする (フルサイズの画像を表示するにはクリック)

次に、Button コントロールと GridView コントロールを Panel にドラッグします。 ボタンの ID プロパティを ProcessShipment に設定し、その Text プロパティを「Process Product Shipment」に設定します。 GridView の ID プロパティを ProductsGrid に設定し、スマート タグから ProductsDataSource という名前の新しい ObjectDataSource にバインドします。 ProductsBLL クラスの GetProducts メソッドからデータをプルするように ObjectDataSource を構成します。 この GridView はデータの表示にのみ使用されるため、[UPDATE]、[INSERT]、[DELETE] タブのドロップダウン リストを [(None)] に設定します。 [完了] をクリックして、データ ソースの構成ウィザードを完了します。

Display the Data Returned from the ProductsBLL Class s GetProducts Method

図 3: ProductsBLL クラスの GetProducts メソッドから返されたデータを表示する (フルサイズの画像を表示するにはクリック)

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

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

ObjectDataSource ウィザードが完了すると、Visual Studio によって、製品データ フィールドの BoundFields と CheckBoxField が追加されます。 ProductNameCategoryNameSupplierNameUnitPriceDiscontinued の各フィールドを除くすべてのフィールドを削除します。 外観は自由にカスタマイズできます。 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] ボタンが表示されます。

The GridView Lists the Products and Offers Sorting and Paging Capabilities

図 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 行のテーブルとして表示されます。

The Inserting Interface is Composed of a Four-Column, Seven-Row Table

図 6: 挿入インターフェイスは、4 列 7 行のテーブルで構成されている (フルサイズの画像を表示するにはクリック)

これで、挿入インターフェイスに Web コントロールを追加する準備ができました。 ツールボックスから 2 つの DropDownList を、サプライヤー用とカテゴリ用のテーブルの適切なセルにドラッグします。

サプライヤー DropDownList の ID プロパティを Suppliers に設定し、これを SuppliersDataSource という名前の新しい ObjectDataSource にバインドします。 新しい ObjectDataSource を構成して、SuppliersBLL クラスの GetSuppliers メソッドからデータを取得し、UPDATE タブのドロップダウン リストを [(None)] に設定します。 [完了] をクリックして、ウィザードを完了します。

Configure the ObjectDataSource to Use the SuppliersBLL Class s GetSuppliers Method

図 7: SuppliersBLL クラスの GetSuppliers メソッドを使用するように ObjectDataSource を構成する (フルサイズの画像を表示するにはクリック)

Suppliers DropDownList に CompanyName データ フィールドを表示し、SupplierID データ フィールドを ListItem の値として使用します。

Display the CompanyName Data Field and Use SupplierID as the Value

図 8: CompanyName データ フィールドを表示し、値として SupplierID を使用する (フルサイズの画像を表示するにはクリック)

2 番目の DropDownList に Categories という名前を付け、CategoriesDataSource という名前の新しい ObjectDataSource にバインドします。 CategoriesDataSource ObjectDataSource が CategoriesBLL クラスの GetCategories メソッドを使用するように構成します。[UPDATE] タブと [DELETE] タブのドロップダウン リストを [(None)] に設定し、[完了] をクリックしてウィザードを完了します。 最後に、DropDownList に CategoryName データ フィールドを表示させ、CategoryID を値として使用します。

これら 2 つの DropDownList が追加され、適切に構成された ObjectDataSource にバインドされると、画面は図 9 のようになります。

The Header Row Now Contains the Suppliers and Categories DropDownLists

図 9: ヘッダー行に SuppliersCategories DropDownLists が含まれるようになりました (フルサイズの画像を表示するにはクリック)

ここでは、新しい各製品の名前と価格を収集する TextBoxes を作成する必要があります。 ツールボックスから 5 つの製品名と価格行のそれぞれについて、TextBox コントロールをデザイナーにドラッグします。 TextBoxes の ID プロパティを、ProductName1UnitPrice1ProductName2UnitPrice2ProductName3UnitPrice3 のように設定します。

各単価の TextBoxes の後に CompareValidator を追加し、ControlToValidate プロパティを適切な ID に設定します。 また、Operator プロパティを GreaterThanEqual に設定し、ValueToCompare を 0 に設定し、TypeCurrency に設定します。 これらの設定は、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 のようになります。

The Inserting Interface Now Includes TextBoxes for the Products Names and Prices

図 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 デザイナーを示しています。

Place the StatusLabel Control Above the Two Panel Controls

図 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] ボタンをクリックすると、クライアント側のメッセージ ボックスの警告が表示されます。

The Inserting Interface is Displayed After Clicking the Process Product Shipment Button

図 12: [Process Product Shipment] ボタンをクリックした後、挿入インターフェイスが表示される (フルサイズの画像を表示するにはクリック)

手順 4: 製品の追加

このチュートリアルの残りの手順は、[Add Products from Shipment] ボタンの Click イベント ハンドラーでデータベースに保存することです。 これを実現するには、ProductsDataTable を作成し、指定された製品名ごとに ProductsRow インスタンスを追加します。 これらの ProductsRow が追加されたら、ProductsDataTable で渡す ProductsBLL クラスの UpdateWithTransaction メソッドを呼び出します。 「トランザクション内のデータベース変更のラップ」チュートリアルで作成された UpdateWithTransaction メソッドは、ProductsDataTableProductsTableAdapterUpdateWithTransaction メソッドに渡すことを思い出してください。 そこから、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 が無効なデータを報告していることを意味します。このような場合は、入力された製品を挿入しないようにする必要があります。そうしないと、ユーザーが入力した単価の値を ProductsRowUnitPrice プロパティに割り当てようとすると、例外が発生します。

次に、新しい ProductsDataTable インスタンスが作成されます (products)。 for ループを使用して製品名と単価の TextBoxes を反復処理し、Text プロパティが、ローカル変数 productNameunitPrice に読み込まれます。 ユーザーが単価の値を入力し、対応する製品名の値を入力していない場合は、StatusLabel にメッセージが表示されます。単価を指定する場合は、製品の名前も含める必要があり、イベント ハンドラーが終了します。

製品名が指定されている場合は、ProductsDataTableNewProductsRow メソッドを使用して新しい ProductsRow インスタンスが作成されます。 この新しい ProductsRow インスタンスの ProductName プロパティは、現在の製品名 TextBox に設定され、一方 SupplierID プロパティと CategoryID プロパティは、挿入インターフェイスのヘッダーの DropDownLists の SelectedValue プロパティに割り当てられます。 ユーザーが製品の価格の値を入力した場合は、ProductsRow インスタンスの UnitPrice プロパティに割り当てられます。それ以外の場合、プロパティは割り当てられないままになり、データベース内の UnitPriceNULL 値になります。 最後に、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 番目の製品は前のページにあります)。

A Product Name is Required When Entering a Unit Price

図 13: 単価を入力するときに製品名が要求される (フルサイズの画像を表示するにはクリック)

Three New Veggies Have Been Added for the Supplier Mayumi s

図 14: サプライヤー Mayumi に 3 つの新しい野菜が追加された (フルサイズの画像を表示するにはクリック)

The New Products Can Be Found in the Last Page of the GridView

図 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行をドロップしてください。