このチュートリアルは、データのバッチの更新、削除、挿入を行う 4 つのうち最初のチュートリアルです。 このチュートリアルでは、データベース トランザクションを使用してバッチ変更をアトミック操作として実行する方法について説明します。これにより、すべてのステップが成功するか、すべてのステップが失敗するかを確認できます。
イントロダクション
「データの挿入、更新、および削除の概要」チュートリアルから見たように、GridView には行レベルの編集と削除が組み込まれています。 マウスを数回クリックするだけで、コードを1行も書かずに、行単位での編集と削除に満足している場合に限り、豊富なデータ変更インターフェースを作成できます。 ただし、特定のシナリオでは、これは不十分であり、レコードのバッチを編集または削除する機能をユーザーに提供する必要があります。
たとえば、ほとんどの Web ベースの電子メール クライアントは、グリッドを使用して各メッセージを一覧表示します。各行には、電子メールの情報 (件名、送信者など) と共にチェック ボックスが含まれています。 このインターフェイスを使用すると、ユーザーは複数のメッセージをチェックし、[選択したメッセージの削除] ボタンをクリックして複数のメッセージを削除できます。 バッチ編集インターフェイスは、ユーザーが一般的に多くの異なるレコードを編集する場合に最適です。 ユーザーが強制的に [編集] をクリックして変更を加え、変更する必要があるレコードごとに [更新] をクリックするのではなく、バッチ編集インターフェイスによって各行が編集インターフェイスでレンダリングされます。 ユーザーは、変更する必要がある行のセットをすばやく変更し、[すべて更新] ボタンをクリックしてこれらの変更を保存できます。 この一連のチュートリアルでは、データのバッチを挿入、編集、および削除するためのインターフェイスを作成する方法について説明します。
バッチ操作を実行するときは、バッチ内の一部の操作が成功し、他の操作が失敗する可能性があるかどうかを判断することが重要です。 インターフェイスを削除するバッチについて考えてみましょう。最初に選択したレコードが正常に削除されたが、外部キー制約違反が原因で 2 つ目のレコードが失敗した場合はどうなりますか? 最初のレコードの削除をロールバックするか、最初のレコードを削除したままにしてもかまいませんか?
バッチ操作を アトミック操作 (すべてのステップが成功するか、すべてのステップが失敗する) として扱う場合は、 データベース トランザクションのサポートを含むようにデータ アクセス層を拡張する必要があります。 データベース トランザクションでは、トランザクションの傘下で実行される一連の INSERT
、 UPDATE
、および DELETE
ステートメントのアトミック性が保証され、ほとんどの最新のデータベース システムでサポートされる機能です。
このチュートリアルでは、DAL を拡張してデータベース トランザクションを使用する方法について説明します。 以降のチュートリアルでは、インターフェイスのバッチ挿入、更新、および削除のための Web ページの実装について説明します。 では、始めましょう。
注
バッチ トランザクション内のデータを変更する場合、アトミック性は常に必要であるとは限りません。 一部のシナリオでは、Web ベースの電子メール クライアントから一連の電子メールを削除する場合など、一部のデータ変更が成功し、同じバッチ内の他の変更が失敗する場合があります。 削除プロセスの途中でデータベース エラーが発生した場合、エラーなしで処理されたレコードが削除されたままである可能性があります。 このような場合、DAL を変更してデータベース トランザクションをサポートする必要はありません。 ただし、アトミック性が不可欠なバッチ操作シナリオは他にもあります。 顧客が 1 つの銀行口座から別の銀行口座に資金を移動する場合は、2 つの操作を実行する必要があります。資金は最初の口座から差し引かれ、次に 2 番目の口座に追加される必要があります。 銀行は最初のステップが成功しても2番目のステップが失敗しても気にしないかもしれませんが、顧客は当然怒ります。 次の 3 つのチュートリアルで構築するインターフェイスの挿入、更新、および削除のバッチでの使用を計画していない場合でも、このチュートリアルを実行し、データベース トランザクションをサポートするための DAL の機能強化を実装することをお勧めします。
トランザクションの概要
ほとんどのデータベースには トランザクションのサポートが含まれており、複数のデータベース コマンドを 1 つの論理作業単位にグループ化できます。 トランザクションを構成するデータベース コマンドはアトミックであることが保証されます。つまり、すべてのコマンドが失敗するか、すべて成功します。
一般に、トランザクションは、次のパターンを使用して SQL ステートメントを使用して実装されます。
- トランザクションの開始を示します。
- トランザクションを構成する SQL ステートメントを実行します。
- 手順 2 のステートメントのいずれかでエラーが発生した場合は、トランザクションをロールバックします。
- 手順 2 のすべてのステートメントがエラーなしで完了した場合は、トランザクションをコミットします。
トランザクションの作成、コミット、ロールバックに使用する SQL ステートメントは、SQL スクリプトの記述時またはストアド プロシージャの作成時に手動で入力するか、ADO.NET または System.Transactions
名前空間のクラスを使用するプログラムによって入力できます。 このチュートリアルでは、ADO.NET を使用したトランザクションの管理のみを確認します。 今後のチュートリアルでは、データ アクセス層でストアド プロシージャを使用する方法について説明します。この時点で、トランザクションを作成、ロールバック、コミットするための SQL ステートメントについて説明します。 それまでの間、詳細については、「 SQL Server ストアド プロシージャでのトランザクションの管理 」を参照してください。
注
TransactionScope
名前空間の System.Transactions
を使用すると、開発者は、トランザクションのスコープ内で一連のステートメントをプログラムでラップでき、2 つの異なるデータベースや、Microsoft SQL Server データベース、Oracle データベース、Web サービスなどの異種データ ストアなど、複数のソースを含む複雑なトランザクションのサポートが含まれます。 ADO.NET はデータベース トランザクションに対してより具体的であり、多くの場合、リソースの負荷がはるかに少ないため、 TransactionScope
クラスではなく、このチュートリアルで ADO.NET トランザクションを使用することにしました。 さらに、特定のシナリオでは、 TransactionScope
クラスは Microsoft 分散トランザクション コーディネーター (MSDTC) を使用します。 MSDTC に関する構成、実装、およびパフォーマンスの問題により、これらのチュートリアルの範囲を超えて、かなり特殊で高度なトピックになります。
ADO.NET で SqlClient プロバイダーを操作する場合、トランザクションは SqlConnection
クラス の BeginTransaction
メソッドを呼び出して開始され、 SqlTransaction
オブジェクトが返されます。 トランザクションを構成するデータ変更ステートメントは、 try...catch
ブロック内に配置されます。
try
ブロック内のステートメントでエラーが発生した場合、catch
オブジェクトのSqlTransaction
メソッドを使用してトランザクションをロールバックできるRollback
ブロックに実行が転送されます。 すべてのステートメントが正常に完了すると、SqlTransaction
ブロックの末尾にある Commit
オブジェクトの try
の呼び出しによってトランザクションがコミットされます。 次のコード スニペットは、このパターンを示しています。
' Create the SqlTransaction object
Dim myTransaction As SqlTransaction = 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
End Try
既定では、型指定された DataSet の TableAdapters はトランザクションを使用しません。 トランザクションのサポートを提供するには、TableAdapter クラスを拡張して、上記のパターンを使用してトランザクションのスコープ内で一連のデータ変更ステートメントを実行する追加のメソッドを含める必要があります。 手順 2 では、部分クラスを使用してこれらのメソッドを追加する方法について説明します。
手順 1: バッチデータを扱うWebページの作成
データベース トランザクションをサポートするように DAL を拡張する方法を調べ始める前に、まず、このチュートリアルに必要な ASP.NET Web ページとその後に続く 3 つの Web ページを作成します。 まず、 BatchData
という名前の新しいフォルダーを追加し、次の ASP.NET ページを追加して、各ページを Site.master
マスター ページに関連付けます。
Default.aspx
Transactions.aspx
BatchUpdate.aspx
BatchDelete.aspx
BatchInsert.aspx
図 1: SqlDataSource-Related チュートリアルの 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 と TableAdapters で構成されています。 DataTable はデータを保持しますが、TableAdapters はデータベースから DataTable にデータを読み取る機能や、DataTable に加えられた変更でデータベースを更新する機能などを提供します。 TableAdapters には、データを更新するための 2 つのパターンが用意されていることを思い出してください。これは、Batch Update と DB-Direct と呼ばれます。 バッチ更新パターンでは、 TableAdapter に DataSet、DataTable、または DataRow のコレクションが渡されます。 このデータは列挙され、挿入、変更、または削除された行ごとに、 InsertCommand
、 UpdateCommand
、または DeleteCommand
が実行されます。 DB-Direct パターンでは、代わりに TableAdapter に、1 つのレコードの挿入、更新、または削除に必要な列の値が渡されます。 その後、DB Direct パターン メソッドは、渡された値を使用して、適切な InsertCommand
、 UpdateCommand
、または DeleteCommand
ステートメントを実行します。
使用される更新パターンに関係なく、TableAdapters 自動生成メソッドはトランザクションを使用しません。 既定では、TableAdapter によって実行される各挿入、更新、または削除は、1 つの個別の操作として扱われます。 たとえば、DB-Direct パターンが BLL の一部のコードでデータベースに 10 個のレコードを挿入するために使用されるとします。 このコードは、TableAdapter の Insert
メソッドを 10 回呼び出します。 最初の 5 回の挿入が成功しても、6 番目の挿入で例外が発生した場合、挿入された最初の 5 つのレコードはデータベースに残ります。 同様に、バッチ更新パターンを使用して DataTable 内の挿入、変更、削除された行に対する挿入、更新、削除を実行した場合、最初のいくつかの変更が成功したが、後でエラーが発生した場合、完了した以前の変更はデータベースに残ります。
特定のシナリオでは、一連の変更にわたって原子性を確保する必要があります。 これを実現するには、トランザクションの傘の下に InsertCommand
、 UpdateCommand
、 DeleteCommand
を実行する新しいメソッドを追加して、TableAdapter を手動で拡張する必要があります。
データ アクセス層の作成では、部分クラスを使用して、型指定された DataSet 内の DataTable の機能を拡張する方法を見てみましょう。 この手法は、TableAdapters でも使用できます。
Typed DataSet Northwind.xsd
は、 App_Code
フォルダー DAL
サブフォルダーにあります。
DAL
という名前のTransactionSupport
フォルダーにサブフォルダーを作成し、ProductsTableAdapter.TransactionSupport.vb
という名前の新しいクラス ファイルを追加します (図 4 を参照)。 このファイルには、トランザクションを使用してデータ変更を実行するためのメソッドを含む ProductsTableAdapter
の部分的な実装が保持されます。
図 4: TransactionSupport
という名前のフォルダーと名前の付いたクラス ファイルを追加する ProductsTableAdapter.TransactionSupport.vb
ProductsTableAdapter.TransactionSupport.vb
ファイルに次のコードを入力します。
Imports System.Data
Imports System.Data.SqlClient
Namespace NorthwindTableAdapters
Partial Public Class ProductsTableAdapter
Private _transaction As SqlTransaction
Private Property Transaction() As SqlTransaction
Get
Return Me._transaction
End Get
Set(ByVal Value As SqlTransaction)
Me._transaction = Value
End Set
End Property
Public Sub BeginTransaction()
' Open the connection, if needed
If Me.Connection.State <> ConnectionState.Open Then
Me.Connection.Open()
End If
' Create the transaction and assign it to the Transaction property
Me.Transaction = Me.Connection.BeginTransaction()
' Attach the transaction to the Adapters
For Each command As SqlCommand In Me.CommandCollection
command.Transaction = Me.Transaction
Next
Me.Adapter.InsertCommand.Transaction = Me.Transaction
Me.Adapter.UpdateCommand.Transaction = Me.Transaction
Me.Adapter.DeleteCommand.Transaction = Me.Transaction
End Sub
Public Sub CommitTransaction()
' Commit the transaction
Me.Transaction.Commit()
' Close the connection
Me.Connection.Close()
End Sub
Public Sub RollbackTransaction()
' Rollback the transaction
Me.Transaction.Rollback()
' Close the connection
Me.Connection.Close()
End Sub
End Class
End Namespace
ここでのクラス宣言の Partial
キーワードは、コンパイラに対して、ProductsTableAdapter
名前空間のNorthwindTableAdapters
クラスに追加されるメンバーをコンパイラに示します。 ファイルの先頭にある Imports System.Data.SqlClient
ステートメントに注意してください。 TableAdapter は SqlClient プロバイダーを使用するように構成されているため、内部的には SqlDataAdapter
オブジェクトを使用してデータベースにコマンドを発行します。 そのため、 SqlTransaction
クラスを使用してトランザクションを開始し、コミットまたはロールバックする必要があります。 Microsoft SQL Server 以外のデータ ストアを使用している場合は、適切なプロバイダーを使用する必要があります。
これらのメソッドは、トランザクションの開始、ロールバック、コミットに必要な構成要素を提供します。 これらは Public
マークされ、 ProductsTableAdapter
内から、DAL 内の別のクラスから、またはアーキテクチャ内の別のレイヤー (BLL など) から使用できるようになります。
BeginTransaction
TableAdapter の内部 SqlConnection
(必要な場合) を開き、トランザクションを開始して Transaction
プロパティに割り当て、トランザクションを内部 SqlDataAdapter
の SqlCommand
オブジェクトにアタッチします。
CommitTransaction
RollbackTransaction
内部Transaction
オブジェクトを閉じる前に、Commit
オブジェクトのRollback
メソッドとConnection
メソッドをそれぞれ呼び出します。
手順 3: トランザクションの傘の下でデータを更新および削除するメソッドを追加する
これらのメソッドが完了したら、トランザクションの傘の下で一連のコマンドを実行するメソッドを ProductsDataTable
または BLL に追加する準備ができました。 次のメソッドでは、バッチ更新パターンを使用して、トランザクションを使用して ProductsDataTable
インスタンスを更新します。
BeginTransaction
メソッドを呼び出してトランザクションを開始し、Try...Catch
ブロックを使用してデータ変更ステートメントを発行します。
Adapter
オブジェクトのUpdate
メソッドの呼び出しによって例外が発生した場合、トランザクションがロールバックされ、例外が再スローされるcatch
ブロックに実行が転送されます。
Update
メソッドは、指定されたProductsDataTable
の行を列挙し、必要なInsertCommand
、UpdateCommand
、DeleteCommand
を実行することによって、バッチ更新パターンを実装していることを思い出してください。 これらのコマンドのいずれかがエラーになった場合、トランザクションはロールバックされ、トランザクションの有効期間中に行われた以前の変更が元に戻されます。
Update
ステートメントがエラーなしで完了した場合、トランザクションは完全にコミットされます。
Public Function UpdateWithTransaction _
(ByVal dataTable As Northwind.ProductsDataTable) As Integer
Me.BeginTransaction()
Try
' Perform the update on the DataTable
Dim returnValue As Integer = Me.Adapter.Update(dataTable)
' If we reach here, no errors, so commit the transaction
Me.CommitTransaction()
Return returnValue
Catch
' If we reach here, there was an error, so rollback the transaction
Me.RollbackTransaction()
Throw
End Try
End Function
UpdateWithTransaction
の部分クラスを使用して、ProductsTableAdapter
メソッドをProductsTableAdapter.TransactionSupport.vb
クラスに追加します。 または、このメソッドをビジネス ロジック レイヤーの ProductsBLL
クラスに追加し、構文を若干変更することもできます。 つまり、Me
、Me.BeginTransaction()
、およびMe.CommitTransaction()
のキーワードMe.RollbackTransaction()
は、Adapter
に置き換える必要があります (Adapter
はProductsBLL
型のプロパティの名前ProductsTableAdapter
です)。
UpdateWithTransaction
メソッドは Batch Update パターンを使用しますが、次のメソッドに示すように、一連の DB-Direct 呼び出しをトランザクションのスコープ内で使用することもできます。
DeleteProductsWithTransaction
メソッドは、削除すべきList(Of T)
を含むInteger
型のProductID
を入力として受け入れます。 メソッドは、BeginTransaction
の呼び出しを介してトランザクションを開始し、Try
ブロックで、指定されたリストを反復処理して、各Delete
値の DB-Direct パターン ProductID
メソッドを呼び出します。
Delete
の呼び出しのいずれかが失敗した場合は、トランザクションがロールバックされ、例外が再スローされるCatch
ブロックに制御が転送されます。
Delete
のすべての呼び出しが成功した場合、トランザクションはコミットされます。 このメソッドを ProductsBLL
クラスに追加します。
Public Sub DeleteProductsWithTransaction _
(ByVal productIDs As System.Collections.Generic.List(Of Integer))
' Start the transaction
Adapter.BeginTransaction()
Try
' Delete each product specified in the list
For Each productID As Integer In productIDs
Adapter.Delete(productID)
Next
' Commit the transaction
Adapter.CommitTransaction()
Catch
' There was an error - rollback the transaction
Adapter.RollbackTransaction()
Throw
End Try
End Sub
複数の TableAdapters にトランザクションを適用する
このチュートリアルで調べるトランザクション関連のコードでは、 ProductsTableAdapter
に対する複数のステートメントをアトミック操作として扱うことが可能です。 しかし、異なるデータベース テーブルに対する複数の変更をアトミックに実行する必要がある場合はどうでしょうか。 たとえば、カテゴリを削除するときに、最初に現在の製品を他のカテゴリに再割り当てすることをお勧めします。 製品の再割り当てとカテゴリの削除の 2 つの手順は、アトミック操作として実行する必要があります。 ただし、 ProductsTableAdapter
には Products
テーブルを変更するためのメソッドのみが含まれており、 CategoriesTableAdapter
には Categories
テーブルを変更するためのメソッドのみが含まれています。 では、トランザクションが両方の TableAdapters をどのように包含できますか?
1 つのオプションは、CategoriesTableAdapter
という名前のDeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
にメソッドを追加し、そのメソッドがストアド プロシージャを呼び出し、ストアド プロシージャ内で定義されているトランザクションのスコープ内で製品を再割り当てし、カテゴリを削除する方法です。 今後のチュートリアルでは、ストアド プロシージャでトランザクションを開始、コミット、ロールバックする方法について説明します。
もう 1 つのオプションは、 DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
メソッドを含む DAL にヘルパー クラスを作成することです。 このメソッドは、 CategoriesTableAdapter
と ProductsTableAdapter
のインスタンスを作成し、これら 2 つの TableAdapters Connection
プロパティを同じ SqlConnection
インスタンスに設定します。 その時点で、2 つの TableAdapters のいずれかが、 BeginTransaction
の呼び出しでトランザクションを開始します。 製品を再割り当てしてカテゴリを削除するための TableAdapters メソッドは、トランザクションがコミットまたはロールバックされた Try...Catch
ブロックで必要に応じて呼び出されます。
手順 4: ビジネス ロジック レイヤーにUpdateWithTransaction
Method を追加する
手順 3 では、DAL のUpdateWithTransaction
にProductsTableAdapter
メソッドを追加しました。 対応するメソッドを BLL に追加する必要があります。 プレゼンテーションレイヤーは DAL に直接呼び出して UpdateWithTransaction
メソッドを呼び出すことができますが、これらのチュートリアルでは、プレゼンテーション層から DAL を絶縁する階層構造の定義に努めています。 したがって、このアプローチを続ける必要があります。
ProductsBLL
クラス ファイルを開き、対応する DAL メソッドを呼び出すUpdateWithTransaction
という名前のメソッドを追加します。
ProductsBLL
には、追加した UpdateWithTransaction
と、手順 3 で追加した DeleteProductsWithTransaction
という 2 つの新しいメソッドが追加されました。
Public Function UpdateWithTransaction _
(ByVal products As Northwind.ProductsDataTable) As Integer
Return Adapter.UpdateWithTransaction(products)
End Function
Public Sub DeleteProductsWithTransaction _
(ByVal productIDs As System.Collections.Generic.List(Of Integer))
' Start the transaction
Adapter.BeginTransaction()
Try
' Delete each product specified in the list
For Each productID As Integer In productIDs
Adapter.Delete(productID)
Next
' Commit the transaction
Adapter.CommitTransaction()
Catch
' There was an error - rollback the transaction
Adapter.RollbackTransaction()
Throw
End Try
End Sub
注
これらのメソッドには、DataObjectMethodAttribute
クラスの他のほとんどのメソッドに割り当てられたProductsBLL
属性は含まれません。これらのメソッドは、ASP.NET ページ分離コード クラスから直接呼び出されるためです。
DataObjectMethodAttribute
は、ObjectDataSource のデータ ソースの構成ウィザードと、どのタブ (SELECT、UPDATE、INSERT、または DELETE) に表示されるメソッドにフラグを設定するために使用されることを思い出してください。 GridView にはバッチ編集または削除の組み込みサポートがないため、コード不要の宣言型アプローチを使用するのではなく、プログラムでこれらのメソッドを呼び出す必要があります。
手順 5: プレゼンテーション層からデータベース データをアトミックに更新する
レコードのバッチを更新するときにトランザクションに与える影響を示すために、GridView 内のすべての製品を一覧表示するユーザー インターフェイスを作成し、ボタン Web コントロールを含めます。ボタン Web コントロールをクリックすると、製品 CategoryID
値が再割り当てされます。 特に、カテゴリの再割り当てが進み、最初のいくつかの製品に有効な CategoryID
値が割り当てられ、他の製品には意図的に存在しない CategoryID
値が割り当てられます。
CategoryID
が既存のカテゴリのCategoryID
と一致しない製品でデータベースを更新しようとすると、外部キー制約違反が発生し、例外が発生します。 この例では、トランザクションを使用すると、外部キー制約違反から発生した例外によって、以前の有効な CategoryID
変更がロールバックされるということです。 ただし、トランザクションを使用しない場合、初期カテゴリに対する変更は残ります。
Transactions.aspx
フォルダー内の BatchData
ページを開くことから始めて、ツールボックスからデザイナーに GridView をドラッグします。 その ID
を Products
に設定し、スマート タグから ProductsDataSource
という名前の新しい ObjectDataSource にバインドします。
ProductsBLL
クラスの GetProducts
メソッドからデータをプルするように ObjectDataSource を構成します。 これは読み取り専用の GridView であるため、[UPDATE]、[INSERT]、[DELETE] タブのドロップダウン リストを [なし] に設定し、[完了] をクリックします。
図 5: ProductsBLL
クラスの GetProducts
メソッドを使用するように ObjectDataSource を構成する (フルサイズの画像を表示する をクリックします)。
図 6: [UPDATE]、[INSERT]、[DELETE] タブの Drop-Down リストを [なし] に設定する (フルサイズの画像を表示する 場合はクリックします)
データ ソースの構成ウィザードが完了すると、Visual Studio によって、製品データ フィールドの BoundFields と CheckBoxField が作成されます。
ProductID
、ProductName
、CategoryID
、CategoryName
を除くすべてのフィールドを削除し、ProductName
プロパティと CategoryName
BoundFields 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 つのボタン Web コントロールを追加します。 最初の Button s Text プロパティをグリッドの更新に設定し、2 番目のプロパティを [カテゴリの変更 (WITH TRANSACTION)] に設定し、3 番目のボタンを [カテゴリの変更 ] (トランザクションなし) に設定します。
<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 つのボタン Web コントロールが含まれています (フルサイズの画像を表示する をクリックします)。
3 つのボタンの Click
イベントのそれぞれにイベント ハンドラーを作成し、次のコードを使用します。
Protected Sub RefreshGrid_Click _
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles RefreshGrid.Click
Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithTransaction_Click _
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles ModifyCategoriesWithTransaction.Click
' Get the set of products
Dim productsAPI As New ProductsBLL()
Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
' Update each product's CategoryID
For Each product As Northwind.ProductsRow In productsData
product.CategoryID = product.ProductID
Next
' Update the data using a transaction
productsAPI.UpdateWithTransaction(productsData)
' Refresh the Grid
Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithoutTransaction_Click _
(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles ModifyCategoriesWithoutTransaction.Click
' Get the set of products
Dim productsAPI As New ProductsBLL()
Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
' Update each product's CategoryID
For Each product As Northwind.ProductsRow In productsData
product.CategoryID = product.ProductID
Next
' Update the data WITHOUT using a transaction
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
productsAdapter.Update(productsData)
' Refresh the Grid
Products.DataBind()
End Sub
Refresh Button Click
イベント ハンドラーは、 Products
GridView の DataBind
メソッドを呼び出すことによって、データを GridView に再バインドするだけです。
2 番目のイベント ハンドラーは、 CategoryID
製品を再割り当てし、BLL の新しいトランザクション メソッドを使用して、トランザクションの傘下でデータベースの更新を実行します。 各製品の CategoryID
は、その ProductID
と同じ値に任意に設定されることに注意してください。 最初のいくつかの製品には、ProductID
値が有効なCategoryID
に偶然マップされるため、正常に動作します。 しかし、 ProductID
が大きくなりすぎると、 ProductID
と CategoryID
の重複は適用されなくなります。
3 番目の Click
イベント ハンドラーは、 CategoryID
製品を同じ方法で更新しますが、 ProductsTableAdapter
の既定の Update
メソッドを使用してデータベースに更新を送信します。 この Update
メソッドはトランザクション内で一連のコマンドをラップしないため、最初に発生した外部キー制約違反エラーの前に行われた変更は、そのまま残ります。
この動作を示すには、ブラウザーからこのページにアクセスしてください。 最初に、図 8 に示すように、データの最初のページが表示されます。 次に、「カテゴリの変更(トランザクション付き)」ボタンをクリックします。 これにより、ポストバックが発生し、すべての製品 CategoryID
値の更新が試行されますが、外部キー制約違反が発生します (図 9 を参照)。
図 8: Pageable GridView に製品が表示されます (フルサイズの画像を表示する をクリックします)。
図 9: 外部キー制約違反のカテゴリの結果を再割り当て (フルサイズの画像を表示する をクリックします)。
ブラウザーの [戻る] ボタンをクリックし、[グリッドの更新] ボタンをクリックします。 データを更新すると、図 8 に示すのとまったく同じ出力が表示されます。 つまり、一部の製品 CategoryID
が有効な値に変更され、データベースで更新された場合でも、外部キー制約違反が発生したときにロールバックされました。
次に、[カテゴリの変更 (トランザクションなし)] ボタンをクリックしてみてください。 これにより、同じ外部キー制約違反エラーが発生します (図 9 を参照)。今回は、 CategoryID
値が有効な値に変更された製品はロールバックされません。 ブラウザーの [戻る] ボタンをクリックし、[グリッドの更新] ボタンをクリックします。 図 10 に示すように、最初の 8 つの製品の CategoryID
が再割り当てされています。 たとえば、図 8 では、Chang の CategoryID
は 1 でしたが、図 10 では 2 に再割り当てされています。
図 10: 一部の製品 CategoryID
値は更新されましたが、他の製品は更新されませんでした (フルサイズの画像を表示するには、ここをクリックします)
概要
既定では、TableAdapter のメソッドは、実行されたデータベース ステートメントをトランザクションのスコープ内でラップしませんが、少しの作業で、トランザクションを作成、コミット、ロールバックするメソッドを追加できます。 このチュートリアルでは、 ProductsTableAdapter
クラスに 3 つのメソッド ( BeginTransaction
、 CommitTransaction
、 RollbackTransaction
) を作成しました。 これらのメソッドを Try...Catch
ブロックと共に使用して、一連のデータ変更ステートメントをアトミックにする方法について説明しました。 特に、UpdateWithTransaction
に ProductsTableAdapter
メソッドを作成しました。このメソッドは、Batch Update パターンを使用して、指定されたProductsDataTable
の行に必要な変更を実行します。 また、DeleteProductsWithTransaction
メソッドを BLL のProductsBLL
クラスに追加しました。このメソッドは、List
値のProductID
を入力として受け取り、各Delete
の DB-Direct パターン メソッドProductID
を呼び出します。 どちらのメソッドも、まずトランザクションを作成してから、 Try...Catch
ブロック内でデータ変更ステートメントを実行します。 例外が発生した場合、トランザクションはロールバックされ、それ以外の場合はコミットされます。
手順 5 では、トランザクション バッチ更新と、トランザクションの使用を怠ったバッチ更新の影響を示しました。 次の 3 つのチュートリアルでは、このチュートリアルで構築した基礎を基にして、バッチ更新、削除、挿入を実行するためのユーザー インターフェイスを作成します。
プログラミングに満足!
もっと読む
この記事で説明したトピックの詳細については、次のリソースを参照してください。
著者について
7 冊の ASP/ASP.NET 書籍の著者であり、4GuysFromRolla.com の創設者である Scott Mitchell は、1998 年から Microsoft Web テクノロジを使用しています。 Scott は、独立したコンサルタント、トレーナー、ライターとして働いています。 彼の最新の本は サムズ・ティーチ・セルフ ASP.NET 24時間で2.0です。 彼には mitchell@4GuysFromRolla.comで連絡できます。
特別な感謝
このチュートリアル シリーズは、多くの役に立つ校閲者によってレビューされました。 このチュートリアルのリード レビュー担当者は、Dave Gardner、Hilton Giesenow、Toria Murphy でした。 今後の MSDN の記事を確認することに関心がありますか? その場合は、mitchell@4GuysFromRolla.comにメッセージを送ってください。