トランザクション内のデータベース変更をラップする (C#)
このチュートリアルは、データのバッチの更新、削除、挿入について確認する 4 つのチュートリアルのうち最初のものです。 このチュートリアルでは、データベース トランザクションを使用してバッチ変更をアトミック操作として実行する方法について説明します。この方法により、すべてのステップが成功するか、すべてのステップが失敗することが保証されます。
はじめに
「データの挿入、更新、削除の概要」チュートリアルから始めて確認したように、GridView には行レベルの編集と削除が組み込まれています。 行単位で編集と削除を行うコンテンツであれば、コード行を記述することなく、マウスを数回クリックするだけで、豊富なデータ変更インターフェイスを作成できます。 ただし、特定のシナリオでは、これでは不十分であるため、レコードのバッチを編集または削除する機能をユーザーに提供する必要があります。
たとえば、ほとんどの Web ベースのメール クライアントでは、各行にメールの情報 (件名、送信者など) と共にチェックボックスが含まれる各メッセージを一覧表示するために、グリッドを使用します。 このインターフェイスを使用すると、ユーザーは複数のメッセージをチェックし、[選択したメッセージの削除] ボタンをクリックすることで、これらのメッセージを削除できます。 バッチ編集インターフェイスは、ユーザーが一般的に多くの異なるレコードを編集する状況に最適です。 [編集] をクリックし、変更を行ない、変更する必要があるレコードごとに [更新] をクリックすることをユーザーに強制するのではなく、バッチ編集インターフェイスにより、その編集インターフェイスを使用して各行がレンダリングされます。 ユーザーは、変更する必要がある一連の行をすばやく変更し、[すべて更新] ボタンをクリックしてこれらの変更を保存できます。 この一連のチュートリアルでは、データのバッチを挿入、編集、および削除するためのインターフェイスを作成する方法について説明します。
バッチ操作を実行するときは、バッチ内の一部の操作が成功する一方、他の操作が失敗することが可能であるどうかを判断することが重要です。 インターフェイスを削除するバッチについて考えてみましょう。最初に選択したレコードが正常に削除されたが、たとえば、外部キー制約違反が原因で 2 つ目のレコードが失敗した場合はどうなるでしょうか? 最初のレコードの削除をロールバックすべきですか、または最初のレコードを削除することが許容されますか?
バッチ操作をアトミック操作 (すべてのステップが成功するか、すべてのステップが失敗する操作) として扱う場合は、データベース トランザクションのサポートが含まれるようにデータ アクセス層を拡張する必要があります。 データベース トランザクションは、トランザクションの傘下で実行される一連の INSERT
、UPDATE
、DELETE
ステートメントのアトミック性を保証し、ほとんどの最新のデータベース システムでサポートされる機能です。
このチュートリアルでは、DAL を拡張してデータベース トランザクションを使用する方法について説明します。 以降のチュートリアルでは、インターフェイスのバッチの挿入、更新、削除のための Web ページの実装について説明します。 では、始めましょう。
Note
バッチ トランザクション内のデータを変更する場合、アトミック性が常に必要であるとは限りません。 一部のシナリオでは、Web ベースのメール クライアントから一連のメールを削除する場合など、一部のデータ変更が成功し、同じバッチ内のその他の変更が失敗することが許容される場合があります。 削除プロセスの途中でデータベース エラーが発生した場合、エラーなしで処理されたレコードが削除されたまま残ることが許容される可能性が高いです。 このような場合、データベース トランザクションをサポートするために DAL を変更する必要はありません。 ただし、アトミック性が不可欠であるその他のバッチ操作シナリオがあります。 顧客が 1 つの銀行口座から別の銀行口座に資金を移動する場合は、2 つの操作を実行する必要があります。すなわち、資金を最初の口座から差し引き、次に 2 番目の口座に追加する必要があります。 銀行は最初のステップが成功したが 2 番目のステップが失敗しても気にしないかもしれませんが、顧客は当然ながら狼狽します。 次の 3 つのチュートリアルで構築するインターフェイスのバッチの挿入、更新、削除でデータベース トランザクションを使用することを計画していない場合でも、このチュートリアルを実行し、データベース トランザクションをサポートするための DAL の機能強化を実装することをお勧めします。
トランザクションの概要
ほとんどのデータベースにはトランザクションのサポートが含まれています。これにより、複数のデータベース コマンドを 1 つの作業論理ユニットにグループ化できます。 トランザクションを構成するデータベース コマンドはアトミックであること、つまり、すべてのコマンドが失敗するか、すべてのコマンドが成功することが保証されています。
一般に、トランザクションは、次のパターンを使用して SQL ステートメントを介して実装されます。
- トランザクションの開始点を示します。
- トランザクションを構成する SQL ステートメントを実行します。
- ステップ 2 のステートメントのいずれかでエラーが発生した場合は、トランザクションをロールバックします。
- ステップ 2 のすべてのステートメントがエラーなしで完了した場合は、トランザクションをコミットします。
トランザクションの作成、コミット、ロールバックに使用する SQL ステートメントは、SQL スクリプトの記述時またはストアド プロシージャの作成時に手動で入力するか、ADO.NET または System.Transactions
名前空間内のクラスを使用するプログラムによる方法を介して入力できます。 このチュートリアルでは、ADO.NET を使用したトランザクションの管理のみについて説明します。 今後のチュートリアルでは、データ アクセス層でストアド プロシージャを使用する方法について説明します。その際、トランザクションを作成、ロールバック、コミットするための SQL ステートメントについて説明します。
Note
System.Transactions
名前空間の TransactionScope
クラスを使用すると、開発者は、トランザクションのスコープ内で一連のステートメントをプログラムでラップできます。このクラスには、2 つの異なるデータベースや、さらには Microsoft SQL Server データベース、Oracle データベース、Web サービスなどの異種データ ストアなど、複数のソースが含まれる複雑なトランザクションに対するサポートが含まれます。 このチュートリアルでは、TransactionScope
クラスの代わりに ADO.NET トランザクションを使用することに決めました。なぜなら、ADO.NET はデータベース トランザクションに対してより具体的で明確であり、多くの場合、リソースの負荷がはるかに少ないからです。 また、特定のシナリオでは、TransactionScope
クラスは Microsoft 分散トランザクション コーディネーター (MSDTC) を使用します。 MSDTC に関する構成、実装、パフォーマンスの問題のため、トピックはこれらのチュートリアルの範囲を超えて、かなり特殊で高度な内容になっています。
ADO.NET で SqlClient プロバイダーを操作する場合、トランザクションは、SqlConnection
クラスの BeginTransaction
メソッドに対する呼び出しを介して開始され、これにより、SqlTransaction
オブジェクトが返されます。 トランザクションを構成するデータ変更ステートメントは、try...catch
ブロック内に配置されます。 try
ブロック内のステートメントでエラーが発生した場合、実行は、SqlTransaction
オブジェクトの Rollback
メソッドを使用してトランザクションをロールバックできる catch
ブロックに転送されます。 すべてのステートメントが正常に完了すると、try
ブロックの末尾にある SqlTransaction
オブジェクトの Commit
メソッドに対する呼び出しにより、トランザクションがコミットされます。 次のコード スニペットは、このパターンを示しています。 「データベースとトランザクションとの整合性の維持」を参照してください。
// Create the SqlTransaction object
SqlTransaction myTransaction = SqlConnectionObject.BeginTransaction();
try
{
/*
* ... Perform the database transaction�s data modification statements...
*/
// If we reach here, no errors, so commit the transaction
myTransaction.Commit();
}
catch
{
// If we reach here, there was an error, so rollback the transaction
myTransaction.Rollback();
throw;
}
既定では、型指定された DataSet 内の TableAdapter はトランザクションを使用しません。 トランザクションをサポートするには、TableAdapter クラスを拡張して、上記のパターンを使用してトランザクションのスコープ内で一連のデータ変更ステートメントを実行する追加のメソッドを含める必要があります。 ステップ 2 では、部分クラスを使用してこれらのメソッドを追加する方法について説明します。
ステップ 1: バッチ処理されたデータの操作に関する Web ページを作成する
DAL を拡張してデータベース トランザクションをサポートする方法を調べ始める前に、まず、少し時間を取って、このチュートリアルとその後に続く 3 つのチュートリアルに必要となる ASP.NET Web ページを作成してみましょう。 まず、BatchData
という名前の新しいフォルダーを追加することから始めてから、次の ASP.NET ページを追加し、各ページを Site.master
マスター ページに関連付けます。
Default.aspx
Transactions.aspx
BatchUpdate.aspx
BatchDelete.aspx
BatchInsert.aspx
図 1: SqlDataSource 関連のチュートリアルの ASP.NET ページを追加する
その他のフォルダーと同様に、Default.aspx
は SectionLevelTutorialListing.ascx
ユーザー コントロールを使用して、セクション内のチュートリアルを一覧表示します。 そのため、このユーザー コントロールをソリューション エクスプローラーからページのデザイン ビューにドラッグして Default.aspx
に追加します。
図 2: SectionLevelTutorialListing.ascx
ユーザー コントロールを Default.aspx
に追加する (クリックするとフルサイズの画像が表示されます)
最後に、これら 4 つのページをエントリとして Web.sitemap
ファイルに追加します。 具体的には、サイト マップ <siteMapNode>
をカスタマイズした後に次のマークアップを追加します。
<siteMapNode title="Working with Batched Data"
url="~/BatchData/Default.aspx"
description="Learn how to perform batch operations as opposed to
per-row operations.">
<siteMapNode title="Adding Support for Transactions"
url="~/BatchData/Transactions.aspx"
description="See how to extend the Data Access Layer to support
database transactions." />
<siteMapNode title="Batch Updating"
url="~/BatchData/BatchUpdate.aspx"
description="Build a batch updating interface, where each row in a
GridView is editable." />
<siteMapNode title="Batch Deleting"
url="~/BatchData/BatchDelete.aspx"
description="Explore how to create an interface for batch deleting
by adding a CheckBox to each GridView row." />
<siteMapNode title="Batch Inserting"
url="~/BatchData/BatchInsert.aspx"
description="Examine the steps needed to create a batch inserting
interface, where multiple records can be created at the
click of a button." />
</siteMapNode>
Web.sitemap
を更新した後、少し時間を取って、ブラウザーを介してチュートリアル Web サイトを表示します。 左側のメニューに、バッチ処理されたデータの操作に関するチュートリアルの項目が含まれるようになりました。
図 3: サイト マップに、バッチ処理されたデータの操作に関するチュートリアルのエントリが含まれるようになった
ステップ 2: データベース トランザクションをサポートするためにデータ アクセス層を更新する
最初のチュートリアル「データ アクセス層を作成する」で説明したように、DAL の型指定された DataSet は DataTable と TableAdapter で構成されています。 DataTable はデータを保持しますが、TableAdapter はデータベースから DataTable にデータを読み取る機能や、DataTable に加えられた変更を使用してデータベースを更新する機能などを提供します。 TableAdapter には、データを更新するために、バッチ更新と DB ダイレクトと呼ばれる 2 つのパターンが用意されていることを思い出してください。 バッチ更新パターンでは、TableAdapter に DataSet、DataTable、または DataRows のコレクションが渡されます。 このデータは列挙され、挿入、変更、削除される行ごとに InsertCommand
、UpdateCommand
、DeleteCommand
が実行されます。 DB ダイレクト パターンの場合、代わりに TableAdapter に、単一レコードの挿入、更新、または削除に必要な列の値が渡されます。 その後、DB ダイレクト パターン メソッドは、渡されたこれらの値を使用して、適切な InsertCommand
、UpdateCommand
、または DeleteCommand
ステートメントを実行します。
使用される更新パターンに関係なく、TableAdapter 自動生成メソッドはトランザクションを使用しません。 既定では、TableAdapter によって実行される各挿入、更新、または削除は、単一の個別操作として扱われます。 たとえば、データベースに 10 個のレコードを挿入するために、BLL の一部のコードで DB ダイレクト パターンが使用されるとします。 このコードは、TableAdapter の Insert
メソッドを 10 回呼び出します。 最初の 5 回の挿入が成功したが、6 回目の挿入で例外が発生した場合、最初に挿入された 5 個のレコードがデータベース内に残ります。 同様に、バッチ更新パターンを使用して DataTable 内の挿入、変更、削除済み行に対する挿入、更新、削除を実行した場合、最初のいくつかの変更が成功したが、後でその 1 つにエラーが発生した場合、完了した以前の変更はデータベース内に残ります。
特定のシナリオでは、一連の変更にわたってアトミック性を確保する必要があります。 これを実現するには、トランザクションの傘下で InsertCommand
、UpdateCommand
、DeleteCommand
を実行する新しいメソッドを追加することで、TableAdapter を手動で拡張する必要があります。 「データ アクセス層を作成する」では、部分クラスを使用して、型指定された DataSet 内の DataTable の機能を拡張する方法を確認しました。 この手法は、TableAdapter でも使用できます。
型指定された DataSet Northwind.xsd
は、App_Code
フォルダーの DAL
サブフォルダーにあります。 DAL
フォルダー内に TransactionSupport
という名前のサブフォルダーを作成し、ProductsTableAdapter.TransactionSupport.cs
という名前の新しいクラス ファイルを追加します (図 4 を参照)。 このファイルには、トランザクションを使用してデータ変更を実行するためのメソッドが含まれる ProductsTableAdapter
の部分的な実装が保持されます。
図 4:TransactionSupport
という名前のフォルダーと ProductsTableAdapter.TransactionSupport.cs
という名前のクラス ファイルを追加する
ProductsTableAdapter.TransactionSupport.cs
ファイルに次のコードを入力します。
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace NorthwindTableAdapters
{
public partial class ProductsTableAdapter
{
private SqlTransaction _transaction;
private SqlTransaction Transaction
{
get
{
return this._transaction;
}
set
{
this._transaction = value;
}
}
public void BeginTransaction()
{
// Open the connection, if needed
if (this.Connection.State != ConnectionState.Open)
this.Connection.Open();
// Create the transaction and assign it to the Transaction property
this.Transaction = this.Connection.BeginTransaction();
// Attach the transaction to the Adapters
foreach (SqlCommand command in this.CommandCollection)
{
command.Transaction = this.Transaction;
}
this.Adapter.InsertCommand.Transaction = this.Transaction;
this.Adapter.UpdateCommand.Transaction = this.Transaction;
this.Adapter.DeleteCommand.Transaction = this.Transaction;
}
public void CommitTransaction()
{
// Commit the transaction
this.Transaction.Commit();
// Close the connection
this.Connection.Close();
}
public void RollbackTransaction()
{
// Rollback the transaction
this.Transaction.Rollback();
// Close the connection
this.Connection.Close();
}
}
}
ここでのクラス宣言の partial
キーワードは、内部に追加されるメンバーが NorthwindTableAdapters
名前空間内の ProductsTableAdapter
クラスに追加されることをコンパイラに示します。 ファイルの上部にある using System.Data.SqlClient
ステートメントに注目してください。 TableAdapter は SqlClient プロバイダーを使用するように構成されているため、内部的には SqlDataAdapter
オブジェクトを使用してデータベースに対するコマンドを発行します。 その結果、SqlTransaction
クラスを使用してトランザクションを開始してから、これをコミットまたはロールバックする必要があります。 Microsoft SQL Server 以外のデータ ストアを使用している場合、適切なプロバイダーを使用する必要があります。
これらのメソッドは、トランザクションの開始、ロールバック、コミットに必要な構成要素を提供します。 これらは public
としてマークされ、ProductsTableAdapter
内から、DAL 内の別のクラスから、またはアーキテクチャ内の別のレイヤー (BLL など) から使用できるようになります。 BeginTransaction
は TableAdapter の内部 SqlConnection
を開き (必要な場合)、トランザクションを開始し、これを Transaction
プロパティに割り当て、トランザクションを内部の SqlDataAdapter
の SqlCommand
オブジェクトにアタッチします。 CommitTransaction
と RollbackTransaction
は、内部の Connection
オブジェクトを閉じる前に、Transaction
オブジェクトの Commit
および Rollback
メソッドをそれぞれ呼び出します。
ステップ 3: トランザクションの傘下にあるデータを更新および削除するメソッドを追加する
これらのメソッドが完了したら、トランザクションの傘下で一連のコマンドを実行するメソッドを ProductsDataTable
または BLL に追加する準備ができます。 次のメソッドは、バッチ更新パターンを使用して、トランザクションを使用して ProductsDataTable
インスタンスを更新します。 これは、BeginTransaction
メソッドを呼び出してトランザクションを開始してから、try...catch
ブロックを使用してデータ変更ステートメントを発行します。 Adapter
オブジェクトの Update
メソッドを呼び出した結果、例外が発生した場合、実行は catch
ブロックに転送され、ここで、トランザクションはロールバックされ、例外は再スローされます。 Update
メソッドは指定された ProductsDataTable
の行を列挙し、必要な InsertCommand
、UpdateCommand
、DeleteCommand
を実行することでバッチ更新パターンを実装することを思い出してください。 これらのコマンドのいずれかがエラーになった場合、トランザクションはロールバックされ、トランザクションの有効期間中に行われた以前の変更が元に戻されます。 Update
ステートメントがエラーなしで完了すると、トランザクション全体がコミットされます。
public int UpdateWithTransaction(Northwind.ProductsDataTable dataTable)
{
this.BeginTransaction();
try
{
// Perform the update on the DataTable
int returnValue = this.Adapter.Update(dataTable);
// If we reach here, no errors, so commit the transaction
this.CommitTransaction();
return returnValue;
}
catch
{
// If we reach here, there was an error, so rollback the transaction
this.RollbackTransaction();
throw;
}
}
ProductsTableAdapter.TransactionSupport.cs
内の部分クラスを使用して UpdateWithTransaction
メソッドを ProductsTableAdapter
クラスに追加します。 または、このメソッドをビジネス ロジック レイヤーの ProductsBLL
クラスに追加し、構文的な変更をいくつか加えることもできます。 つまり、this.BeginTransaction()
、this.CommitTransaction()
、this.RollbackTransaction()
内のこのキーワードを Adapter
に置き換える必要があります (Adapter
は型 ProductsTableAdapter
の ProductsBLL
内のプロパティの名前であることを思い出してください)。
UpdateWithTransaction
メソッドはバッチ更新パターンを使用しますが、次のメソッドに示すように、一連の DB ダイレクト呼び出しをトランザクションのスコープ内で使用することもできます。 DeleteProductsWithTransaction
メソッドは、型 int
の List<T>
を入力として受け取ります。これは削除対象の ProductID
です。 このメソッドは、BeginTransaction
への呼び出しを介してトランザクションを開始し、try
ブロック内で、提供されたリストを反復処理しながら ProductID
値ごとに DB ダイレクト パターンの Delete
メソッドを呼び出します。 Delete
への呼び出しのいずれかが失敗した場合、コントロールは catch
ブロックに転送され、ここで、トランザクションはロールバックされ、例外は再スローされます。 Delete
への呼び出しがすべて成功した場合、トランザクションはコミットされます。 このメソッドを ProductsBLL
クラスに追加します。
public void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
{
Adapter.Delete(productID);
}
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
複数の TableAdapter 全体にトランザクションを適用する
このチュートリアルで説明するトランザクション関連のコードでは、アトミック操作として扱う ProductsTableAdapter
に対して複数のステートメントを使用できます。 しかし、異なるデータベース テーブルに対する複数の変更をアトミックに実行する必要がある場合はどうでしょうか? たとえば、カテゴリを削除するときに、最初に現在の製品をその他のカテゴリに再割り当てすることが必要な場合があります。 製品の再割り当てとカテゴリの削除の 2 つのステップは、アトミック操作として実行する必要があります。 ただし、ProductsTableAdapter
には Products
テーブルを変更するためのメソッドのみが含まれ、CategoriesTableAdapter
には Categories
テーブルを変更するためのメソッドのみが含まれます。 では、どうすれば 1 つのトランザクションで両方の TableAdapter を網羅できるでしょうか?
1 つのオプションでは、DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
という名前の CategoriesTableAdapter
にメソッドを追加し、そのメソッドにストアド プロシージャを呼び出させます。このストアド プロシージャは、このストアド プロシージャ内に定義されているトランザクションのスコープ内で、製品の再割り当てとカテゴリの削除の両方を実行します。 今後のチュートリアルでは、ストアド プロシージャ内でトランザクションを開始、コミット、ロールバックする方法について説明します。
もう 1 つのオプションでは、DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
メソッドが含まれるヘルパー クラスを DAL に作成します。 このメソッドは、CategoriesTableAdapter
と ProductsTableAdapter
のインスタンスを作成し、これら 2 つの TableAdapter の Connection
プロパティを同じ SqlConnection
インスタンスに設定します。 その時点で、2 つの TableAdapter の 1 つが、BeginTransaction
への呼び出しを使用してトランザクションを開始します。 製品の再割り当てとカテゴリの削除用の TableAdapter メソッドは、必要に応じてコミットまたはロールバックされたトランザクションを使用して try...catch
ブロックで呼び出されます。
ステップ 4: ビジネス ロジック レイヤーに UpdateWithTransaction
メソッドを追加する
ステップ 3 では、DAL 内の ProductsTableAdapter
に UpdateWithTransaction
メソッドを追加しました。 BLL には、対応するメソッドを追加する必要があります。 プレゼンテーション レイヤーは下位の DAL を直接呼び出して UpdateWithTransaction
メソッドを呼び出すことができますが、これらのチュートリアルでは、プレゼンテーション レイヤーから DAL を分離する複数レイヤー型アーキテクチャを定義しようと努めました。 したがって、このアプローチを続ける必要があります。
ProductsBLL
クラス ファイルを開き、対応する下位の DAL メソッドを単純に呼び出す UpdateWithTransaction
という名前のメソッドを追加します。 これで、ProductsBLL
内には、追加したばかりの UpdateWithTransaction
とステップ 3 で追加した DeleteProductsWithTransaction
の 2 つの新しいメソッドがあるはずです。
public int UpdateWithTransaction(Northwind.ProductsDataTable products)
{
return Adapter.UpdateWithTransaction(products);
}
public void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
Adapter.Delete(productID);
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
Note
これらのメソッドには、ProductsBLL
クラス内のその他のほとんどのメソッドに割り当てられた DataObjectMethodAttribute
属性は含まれていません。なぜなら、これらのメソッドは ASP.NET ページの分離コード クラスから直接呼び出されるためです。 DataObjectMethodAttribute
は、ObjectDataSource の [データ ソースの構成] ウィザード 内に、およびどのタブ (SELECT、UPDATE、INSERT、または DELETE) に、どのようなメソッドを表示すべきかに関するフラグを設定するために使用されることを思い出してください。 GridView にはバッチ編集または削除に対するサポートが組み込まれていないため、コード不要の宣言型アプローチを使用するのではなく、これらのメソッドをプログラムで呼び出す必要があります。
ステップ 5: プレゼンテーション レイヤーからデータベース データをアトミックに更新する
レコードのバッチを更新するときにトランザクションが与える影響を示すために、GridView 内のすべての製品を一覧表示し、クリックされたときに製品の CategoryID
値を再割り当てする Button Web コントロールが含まれるユーザー インターフェイスを作成しましょう。 特に、カテゴリの再割り当ては、最初の複数の製品に有効な CategoryID
値が割り当てられ、その他の製品には存在しない CategoryID
値が意図的に割り当てられるよう進行します。 CategoryID
が既存のカテゴリの CategoryID
と一致しない製品を使用してデータベースを更新しようとすると、外部キー制約違反が発生し、例外が発生します。 この例でわかることは、トランザクションを使用する場合、外部キー制約違反から発生した例外が原因で、以前の有効な CategoryID
の変更がロールバックされるということです。 ただし、トランザクションを使用しない場合、初期カテゴリに対する変更は残ります。
BatchData
フォルダー内の Transactions.aspx
ページを開くことから始めて、ツールボックスからデザイナーに GridView をドラッグします。 その ID
を Products
に設定し、スマート タグから、ProductsDataSource
という名前の新しい ObjectDataSource にバインドします。 ProductsBLL
クラスの GetProducts
メソッドからデータをプルするように ObjectDataSource を構成します。 これは読み取り専用の GridView になるため、[UPDATE]、[INSERT]、[DELETE] の各タブのドロップダウン リストを [(None)] に設定し、[完了] をクリックします。
図 5: ProductsBLL
クラスの GetProducts
メソッドを使用するように ObjectDataSource を構成する (クリックするとフルサイズの画像が表示されます)
図 6: [UPDATE]、[INSERT]、[DELETE] の各タブのドロップダウン リストを [(None)] に設定する (クリックするとフルサイズの画像が表示されます)
[データ ソースの構成] ウィザードが完了すると、Visual Studio により、製品データ フィールドの BoundField と CheckBoxField が作成されます。 ProductID
、ProductName
、CategoryID
、CategoryName
を除くこれらのフィールドをすべて削除し、ProductName
および CategoryName
BoundField の HeaderText
プロパティの名前を Product と Category にそれぞれ変更します。 スマート タグで、[ページングを有効にする] オプションにチェックを付けます。 これらの変更を行った後、GridView と ObjectDataSource の宣言型マークアップは次のようになります。
<asp:GridView ID="Products" runat="server" AllowPaging="True"
AutoGenerateColumns="False" DataKeyNames="ProductID"
DataSourceID="ProductsDataSource">
<Columns>
<asp:BoundField DataField="ProductID" HeaderText="ProductID"
InsertVisible="False" ReadOnly="True"
SortExpression="ProductID" />
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID"
SortExpression="CategoryID" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>
次に、GridView の上に 3 つの Button Web コントロールを追加します。 最初の Button の Text プロパティを Refresh Grid に設定し、2 番目を Modify Categories (WITH TRANSACTION) に設定し、3 番目を Modify Categories (WITHOUT TRANSACTION) に設定します。
<p>
<asp:Button ID="RefreshGrid" runat="server" Text="Refresh Grid" />
</p>
<p>
<asp:Button ID="ModifyCategoriesWithTransaction" runat="server"
Text="Modify Categories (WITH TRANSACTION)" />
</p>
<p>
<asp:Button ID="ModifyCategoriesWithoutTransaction" runat="server"
Text="Modify Categories (WITHOUT TRANSACTION)" />
</p>
この時点で、Visual Studio のデザイン ビューは図 7 に示すスクリーン ショットのようになります。
図 7: ページに GridView と 3 つの Button Web コントロールが含まれている (クリックするとフルサイズの画像が表示されます)
3 つのボタンの Click
イベントのイベント ハンドラーを作成し、次のコードを使用します。
protected void RefreshGrid_Click(object sender, EventArgs e)
{
Products.DataBind();
}
protected void ModifyCategoriesWithTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data using a transaction
productsAPI.UpdateWithTransaction(products);
// Refresh the Grid
Products.DataBind();
}
protected void ModifyCategoriesWithoutTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data WITHOUT using a transaction
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
productsAdapter.Update(products);
// Refresh the Grid
Products.DataBind();
}
更新用ボタンの Click
イベント ハンドラーは、Products
GridView の DataBind
メソッドを呼び出すことで、単純にデータを GridView に再バインドします。
2 番目のイベント ハンドラーは、製品の CategoryID
を再割り当てし、BLL の新しいトランザクション メソッドを使用して、トランザクションの傘下のデータベースの更新を実行します。 各製品の CategoryID
はその ProductID
と同じ値に恣意的に設定されることに注意してください。 これは最初のいくつかの製品に対して正常に動作します。なぜなら、これらの製品には、有効な CategoryID
にマッピングされる ProductID
値があるからです。 しかし、ProductID
が大きくなりすぎ始めると、ProductID
と CategoryID
のこの偶然の重複は適用されなくなります。
3 番目の Click
イベント ハンドラーは、製品の CategoryID
を同じ方法で更新しますが、ProductsTableAdapter
の既定の Update
メソッドを使用してデータベースに更新を送信します。 この Update
メソッドはトランザクション内の一連のコマンドをラップしないため、最初に発生した外部キー制約違反エラーが永続化される前に、これらの変更が行われます。
この動作を実際に確認するには、ブラウザーを介してこのページにアクセスしてください。 最初に、図 8 に示すように、データの最初のページが表示されます。 次に、[Modify Categories (WITH TRANSACTION)] ボタンをクリックします。 これによりポストバックが発生し、すべての製品の CategoryID
値の更新が試みられますが、外部キー制約違反が発生します (図 9 を参照)。
図 8: 製品がページング可能な GridView に表示される (クリックするとフルサイズの画像が表示されます)
図 9: カテゴリの結果を再割り当てすると、外部キー制約違反が発生する (クリックするとフルサイズの画像が表示されます)
ブラウザーの [戻る] ボタンをクリックし、[Refresh Grid] ボタンをクリックします。 データを更新すると、図 8 に示すものとまったく同じ出力が表示されます。 つまり、一部の製品の CategoryID
が有効な値に変更され、データベースで更新された場合でも、外部キー制約違反が発生したときにこれらはロールバックされました。
ここで、[Modify Categories (WITHOUT TRANSACTION)] ボタンをクリックしてみてください。 この結果、同じ外部キー制約違反エラーが発生します (図 9 を参照)。ただし、今回は、CategoryID
値が有効な値に変更された製品はロールバックされません。 ブラウザーの [戻る] ボタンをクリックし、[Refresh Grid] ボタンをクリックしてください。 図 10 に示すように、最初の 8 個の製品の CategoryID
が再割り当てされています。 たとえば、図 8 では、Chang の CategoryID
は 1 でしたが、図 10 では 2 に再割り当てされています。
図 10: 一部の製品の CategoryID
値は更新されたが、その他の製品では更新されなかった (クリックするとフルサイズの画像が表示されます)
まとめ
既定では、TableAdapter のメソッドは、トランザクションのスコープ内で実行されたデータベース ステートメントをラップしません。しかし、少しの作業で、トランザクションを作成、コミット、ロールバックするメソッドを追加できます。 このチュートリアルでは、このようなメソッドとして BeginTransaction
、CommitTransaction
、RollbackTransaction
の 3 つのメソッドを ProductsTableAdapter
クラス内に作成しました。 これらのメソッドを try...catch
ブロックと共に使用して一連のデータ変更ステートメントをアトミックにする方法について説明しました。 特に、バッチ更新パターンを使用して指定された行に必要な変更を実行する UpdateWithTransaction
メソッドを ProductsTableAdapter
内に作成しました。 また、BLL 内の ProductsBLL
クラスに DeleteProductsWithTransaction
メソッドを追加しました。このメソッドは、ProductID
値の List
を入力として受け入れ、ProductID
ごとに DB ダイレクト パターン メソッド Delete
を呼び出します。 どちらのメソッドも最初に、トランザクションを作成してから try...catch
ブロック内でデータ変更ステートメントを実行します。 例外が発生した場合、トランザクションはロールバックされ、それ以外の場合はコミットされます。
ステップ 5 では、トランザクション バッチ更新の影響と、トランザクションの使用を怠ったバッチ更新の影響を示しました。 次の 3 つのチュートリアルでは、このチュートリアルで築いた基盤に基づいて構築し、バッチ更新、削除、挿入を実行するためのユーザー インターフェイスを作成します。
プログラミングに満足!
もっと読む
この記事で説明したトピックの詳細については、次のリソースを参照してください。
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを扱っています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズは24時間で2.0 ASP.NET 自分自身を教えています。 にアクセスするか、ブログを使用して にアクセスmitchell@4GuysFromRolla.comできます。これは でhttp://ScottOnWriting.NET見つけることができます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのレビュー リーダーは、Dave Gardner、Hilton Giesenow、Teresa Murphy でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、 にmitchell@4GuysFromRolla.com行をドロップしてください。