このチュートリアルは、データのバッチの更新、削除、挿入を行う 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.aspxTransactions.aspxBatchUpdate.aspxBatchDelete.aspxBatchInsert.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: ビジネス ロジック レイヤーにUpdateWithTransactionMethod を追加する
手順 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にメッセージを送ってください。