將資料庫修改包裝在交易中 (C#)
演講者:Scott Mitchell
本教學課程是四個教學課程中的第一個,將探討更新、刪除和插入批次資料的程序。 在本教學課程中,我們了解資料庫交易如何允許批次修改作為原子操作執行,以確保所有步驟都成功或所有步驟都失敗。
簡介
正如我們在「插入、更新和刪除資料概述」教學課程中一開始看到的,GridView 提供了對行級編輯和刪除的內建支援。 只要點擊幾下滑鼠,就可以建立豐富的資料修改介面,而無需編寫任何程式碼,只要您能接受逐行進行編輯和刪除。 然而,在某些情況下,這樣的功能是不夠的,我們需要提供使用者編輯或刪除批次記錄的能力。
例如,大多數 Web 式電子郵件用戶端會使用網格來列出每封郵件,每行都包括一個復選框以及電子郵件的資訊 (主旨、寄件者等)。 此介面允許使用者透過檢查多個訊息,然後點擊「刪除所選訊息」按鈕來刪除多個訊息。 批次編輯介面非常適合使用者經常編輯許多不同記錄的情況。 批次編輯介面不會強制使用者點擊「編輯」,進行更改,然後針對需要修改的每筆記錄點擊「更新」,而是使用其編輯介面呈現每一行。 使用者可以快速修改需要變更的行集,然後透過按一下「全部更新」按鈕儲存這些變更。 在這組教學課程中,我們將研究如何建立用於插入、編輯和刪除批次資料的介面。
執行批次操作時,確定批次中的某些操作是否可能成功而其他操作是否失敗非常重要。 考慮批次刪除介面 - 如果第一個選定記錄成功刪除,但第二個記錄失敗 (例如,由於違反外部索引鍵限制),會發生什麼情況? 是否應該回復第一筆記錄的刪除,或是第一筆記錄保持刪除狀態是否可以接受?
如果您希望將批次操作視為原子操作 (所有步驟都成功或所有步驟都失敗),則需要增強資料存取層以包含對資料庫交易的支援。 資料庫交易保證在交易範圍內執行的一組 INSERT
、UPDATE
和 DELETE
陳述式的原子性,並且是大多數現代資料庫系統都支援的功能。
在本教學課程中,我們將了解如何擴展 DAL 以使用資料庫交易。 後續教學課程將探討如何實現網頁批次插入、更新、刪除介面。 讓我們開始吧!
注意
在批次交易中修改資料時,並不總是需要原子性。 在某些情況下,某些資料修改成功而同一批次中的其他資料修改失敗可能是可以接受的,例如從 Web 式電子郵件用戶端刪除一組電子郵件時。 如果在刪除程序中途出現資料庫錯誤,那麼已成功處理的記錄仍然被刪除通常是可以接受的。 在這種情況下,不需要修改 DAL 來支援資料庫交易。 然而,還有其他批次操作場景,其中原子性至關重要。 當客戶將資金從一個銀行帳戶轉移到另一個時,必須執行兩個操作:首先從第一個帳戶扣除資金,然後將其新增到第二個帳戶。 雖然銀行可能不介意第一步成功但第二步失敗,但客戶肯定會感到不滿。 請仔細閱讀本教學課程,並對資料存取層 (DAL) 進行增強,以支援資料庫交易,即使您不打算在接下來三個教學課程中建構的批次插入、更新和刪除介面中使用它們。
交易概覽
大多數資料庫都支援交易,這使得多個資料庫命令可以分組為單一邏輯工作單元。 組成交易的資料庫命令保證是原子的,這代表要麼所有命令都會失敗,要麼所有命令都會成功。
一般來說,交易是透過 SQL 陳述式使用以下模式來實現的:
- 指示交易的開始。
- 執行構成交易的 SQL 陳述式。
- 如果步驟 2 中的陳述式之一出現錯誤,則復原交易。
- 如果步驟 2 中的所有陳述式都完成且沒有錯誤,則提交交易。
用於建立、提交和回復交易的 SQL 陳述式可以在編寫 SQL 指令碼或建立儲存程序時手動輸入,或者通程序式方式使用 ADO.NET 或System.Transactions
命名空間中的類別來實現。 在本教學課程中,我們將只研究使用 ADO.NET 管理交易。 在以後的教學課程中,我們將了解如何在資料存取層中使用預存程序,屆時我們將探索用於建立、回復和提交交易的 SQL 陳述式。
注意
System.Transactions
命名空間中的 TransactionScope
類別使開發人員能夠以程式設計方式包裝交易範圍內的一系列陳述式,並支援涉及多個來源的複雜交易,例如兩個不同的資料庫,甚至異質類型的資料儲存,例如 Microsoft SQL Server 資料庫、Oracle 資料庫和 Web 服務。 我決定在本教學課程中使用 ADO.NET 交易而不是 TransactionScope
類別,因為 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;
}
預設情況下,類型化資料集中的 TableAdapter 不會使用交易。 為了支援交易,我們需要擴展 TableAdapter 類別,新增使用上述模式的額外方法,以在交易範圍內執行一系列資料修改陳述式。 在步驟 2 中,我們將了解如何使用部分類別來新增這些方法。
步驟 1:建立使用大量資料的網頁
在我們開始探討如何擴展資料存取層 (DAL) 以支援資料庫交易之前,先花一些時間建立本教學課程及接下來三個教學課程所需的 ASP.NET 網頁。 首先新增一個名為 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
(點擊查看完整圖片)
最後,將這四個頁面新增為 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
更新後,花點時間透過瀏覽器查看教學課程網站。 左側的功能表現在包含用於處理批次資料教學課程的項目。
圖 3:網站地圖現在包含「使用批次資料」教學課程的項目
步驟 2:更新資料存取層以支援資料庫交易
正如我們在第一個教學課程「建立資料存取層」中討論的那樣,DAL 中的類型化資料集由 DataTable 和 TableAdapter 組成。 DataTables 用於儲存資料,而 TableAdapters 則提供從資料庫讀取資料到 DataTables、將對 DataTables 所做的更改更新到資料庫等功能。 回想一下,TableAdapter 提供了兩種更新資料的模式,我稱之為「批次更新」和「直接資料庫更新」。 使用批次更新模式時,TableAdapter 會傳遞 DataSet、DataTable 或 DataRow 集合。 這些資料會被列舉,對於每一行的插入、修改或刪除,都會執行 InsertCommand
、UpdateCommand
或 DeleteCommand
。 使用「直接資料庫更新」模式時,TableAdapter 會接收插入、更新或刪除單一記錄所需的列值。 然後,「直接資料庫更新」模式方法會使用這些傳入的值來執行相應的 InsertCommand
、UpdateCommand
或 DeleteCommand
陳述式。
無論使用哪種更新模式,TableAdapters 自動產生的方法都不會使用交易。 預設情況下,TableAdapter 執行的每個插入、更新或刪除都視為單一離散操作。 例如,假設 BLL 中的某些程式碼使用「直接資料庫更新」模式將 10 筆記錄插入資料庫。 此程式碼將會呼叫 TableAdapter 的 Insert
方法十次。 如果前五個插入成功,但第六個插入導致例外狀況,則前五個插入的記錄將保留在資料庫中。 同樣地,如果使用批次更新模式來對 DataTable 中的插入、修改和刪除行進行操作,則如果前幾個修改成功,但後來的一個遇到錯誤,則已完成的早期修改將保留在資料庫中。
在某些情況下,我們希望確保一系列修改過的原子性。 為了實現這一點,我們必須透過新增在交易範圍內執行 InsertCommand
、UpdateCommand
和 DeleteCommand
的新方法來手動擴展 TableAdapter。 在建立資料存取層中,我們研究了使用部分類別來擴展類型化資料集中資料表的功能。 此技術也可以與 TableAdapter 一起使用。
類型化資料集 Northwind.xsd
位於 App_Code
資料夾的 DAL
子資料夾中。 在名為 TransactionSupport
的 DAL
資料夾中建立一個子資料夾,並新增一個名為 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
關鍵字在類別宣告中告訴編譯器,所新增的成員將被新增到 ProductsTableAdapter
類別中,該類別位於 NorthwindTableAdapters
命名空間中。 請注意檔案頂部的 using 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 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()
中的 this 關鍵字需要替換為 Adapter
(請記住,Adapter
是 ProductsBLL
中類型為 ProductsTableAdapter
的屬性名稱)。
UpdateWithTransaction
方法使用「批次更新」模式,但也可以在交易範圍內使用一系列「直接資料庫更新」呼叫,如下列方法所示。 DeleteProductsWithTransaction
方法接受 int
類型的 List<T>
作為輸入,其為要刪除的 ProductID
。 此方法透過呼叫 BeginTransaction
來啟動交易,然後在 try
區塊中迭代提供的清單,為每個 ProductID
值呼叫「直接資料庫更新」模式 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
的多個陳述式視為原子操作。 但是,如果需要以原子方式執行不同資料庫表的多次修改怎麼辦? 例如,當刪除一個類別時,我們可能首先想要將其目前產品重新指派給其他類別。 重新分配產品和刪除類別這兩個步驟應作為原子操作執行。 但 ProductsTableAdapter
只包括用於修改 Products
表的方法,而 CategoriesTableAdapter
則只包括修改 Categories
表的方法。 那麼一個交易如何包含兩個 TableAdapter 呢?
一種選擇是向名為 DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
的 CategoriesTableAdapter
新增一個方法,並讓該方法呼叫預存程序,該過程既重新分配產品,又刪除在預存程序中定義的交易範圍內的類別。 我們將在以後的教學課程中了解如何在預存程序中開始、提交和回復交易。
另一種選擇是在包含 DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
方法的 DAL 中建立輔助類別。 此方法將建立 CategoriesTableAdapter
和 ProductsTableAdapter
的執行個體,然後將這兩個 TableAdapters Connection
屬性設定為同一個 SqlConnection
執行個體。 此時,兩個 TableAdapter 之一將透過呼叫 BeginTransaction
來啟動交易。 用於重新分配產品和刪除類別的 TableAdapters 方法將在 try...catch
區塊中呼叫,並根據需要提交或回復交易。
步驟 4:將 UpdateWithTransaction
方法新增至商務邏輯層
在步驟 3 中,我們為 DAL 中的 ProductsTableAdapter
加入了 UpdateWithTransaction
方法。 我們應該要為 BALL 新增一個對應的方法。 雖然表示層可以直接向下呼叫 DAL 來呼叫 UpdateWithTransaction
方法,但這些教學課程努力定義一個分層架構,將 DAL 與表示層隔離。 因此,我們有必要繼續這種做法。
開啟 ProductsBLL
類別檔案並新增一個名為 UpdateWithTransaction
的方法,該方法僅呼叫對應的 DAL 方法。 現在 ProductsBLL
中應該有兩個新方法:一個是剛剛新增的 UpdateWithTransaction
,和步驟 3 中新增的 DeleteProductsWithTransaction
。
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;
}
}
注意
這些方法不包括指派給 ProductsBLL
類別中大多數其他方法的 DataObjectMethodAttribute
屬性,因為我們將直接從 ASP.NET 頁面程式碼後置類別叫用這些方法。 請記得,DataObjectMethodAttribute
用於標記哪些方法應出現在 ObjectDataSource 的「設定資料來源」精靈中,以及它們應該出現在哪些標籤 (選擇、更新、插入或刪除) 下。 由於 GridView 缺乏內建的批次編輯或刪除支援,我們需要以程式方式呼叫這些方法,而不是使用無程式碼的宣告式方法。
步驟 5:從表示層自動更新資料庫資料
為了說明在更新一批記錄時交易的影響,讓我們建立一個使用者介面,列出所有產品在 GridView 中,並包含一個按鈕 Web 控制項,當點擊時,會重新分配產品的 CategoryID
值。 具體而言,類別重新分配的程序將是,前幾個產品會分配一個有效的 CategoryID
值,而其他產品則故意分配一個不存在的 CategoryID
值。 如果我們嘗試更新資料庫中的產品,其 CategoryID
不符合現有類別的 CategoryID
,則會發生外部索引鍵限制違反,並且會擲回例外狀況。 在這個例子中,我們將看到,當使用交易時,由於外部索引鍵限制衝突而引發的例外狀況會導致先前有效的 CategoryID
變更被回復。 然而,當不使用交易時,對初始類別的修改將保留。
首先打開 BatchData
資料夾中的 Transactions.aspx
頁面,然後將 GridView 從工具箱拖曳到設計工具上。 將其 ID
設為 Products
,並從其智慧標籤將其繫結到名為 ProductsDataSource
的新 ObjectDataSource。 設定 ObjectDataSource 以從 ProductsBLL
類別的 GetProducts
方法中提取其資料。 這將是唯讀 GridView,因此將 UPDATE、INSERT 和 DELETE 標籤中的下拉式清單設為 (無),然後按一下完成。
圖 5:設定 ObjectDataSource 以使用 ProductsBLL
類別的 GetProducts
方法 (點擊查看完整圖片)
圖 6:將 UPDATE、INSERT 和 DELETE 標籤中的下拉式清單設為 (無) (點擊查看完整圖片)
完成「設定資料來源」精靈後,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 上方新增三個 Button Web 控制項。 將第一個按鈕的文字屬性設為「重新整理網格」,將第二個按鈕的「文字」屬性設為「修改類別 (有交易) 」,將第三個按鈕的「文字」屬性設為「修改類別 (無交易) 」。
<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 中的 Design 檢視應類似於圖 7 所示的螢幕擷取畫面。
圖 7:該頁面包含一個 GridView 和三個按鈕 Web 控制項 (點擊查看完整圖片)
為三個按鈕的 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。
第二個事件處理常式重新指派產品的 CategoryID
,並使用 BLL 中的新交易方法在交易的保護下執行資料庫更新。 請注意,每個產品的 CategoryID
會任意設定為與其 ProductID
相同的值。 這對於前幾個產品來說效果很好,因為這些產品的 ProductID
值恰好對應到有效的 CategoryID
。 但一旦 ProductID
開始變得過大,這種ProductID
和 CategoryID
的巧合重疊就不再適用。
第三個 Click
事件處理常式以相同的方式更新產品的 CategoryID
,但使用 ProductsTableAdapter
的預設 Update
方法將更新傳送到資料庫。 此 Update
方法不會將一系列命令包裝在交易中,因此在第一次遇到外部索引鍵限制衝突錯誤之前所做的變更將持續存在。
若要示範此行為,請透過瀏覽器造訪此頁面。 最初您應該會看到第一頁資料,如圖 8 所示。 接下來,點擊修改類別 (帶有交易) 按鈕。 這將導致回傳並嘗試更新所有產品的 CategoryID
值,但會導致外部索引鍵限制衝突 (請參閱圖 9)。
圖 8:產品顯示在可分頁的 GridView 中 (點擊查看完整圖片)
圖 9:重新分配類別會導致違反外部索引鍵限制 (點擊查看完整圖片)
現在點擊瀏覽器的「後退」按鈕,然後按一下「重新整理網格」按鈕。 重新整理資料後,您應該會看到與圖 8 所示完全相同的輸出。 也就是說,即使某些產品的 CategoryID
改為合法值並在資料庫中更新,但當發生外部索引鍵限制違規時,它們還是會回復。
現在嘗試點擊「修改類別 (無交易) 」按鈕。 這將導致相同的外部索引鍵限制違規錯誤 (請參閱圖 9),但這次那些 CategoryID
值已變更為合法值的產品將不會回復。 點擊瀏覽器的「返回」按鈕,然後點擊「重新整理網格」按鈕。 如圖 10 所示,前 8 個產品的 CategoryID
已被重新分配。 例如,在圖 8 中,Chang 的 CategoryID
為 1,但在圖 10 中,它被重新分配為 2。
圖 10:某些產品的 CategoryID
值已更新,而其他產品則未更新 (點擊查看完整圖片)
摘要
預設情況下,TableAdapter 的方法不會將執行的資料庫陳述式包裝在交易範圍內,但透過一些工作,我們可以新增建立、提交和回復交易的方法。 在本教學課程中,我們在 ProductsTableAdapter
類別中建立了三個這樣的方法:BeginTransaction
、CommitTransaction
和 RollbackTransaction
。 我們了解如何將這些方法與 try...catch
區塊一起使用來使一系列資料修改陳述式原子化。 特別是,我們在 ProductsTableAdapter
中建立了 UpdateWithTransaction
方法,該方法使用批次更新模式對提供的 ProductsDataTable
的行執行必要的修改。 我們還在 BLL 中的 ProductsBLL
類別中新增了 DeleteProductsWithTransaction
方法,該方法接受 ProductID
值的 List
作為其輸入,並為每個 ProductID
呼叫「直接資料庫更新」模式的方法 Delete
。 兩種方法都會先建立交易,然後執行 try...catch
區塊內的資料修改陳述式。 如果發生例外狀況,則交易回復,否則會進行提交。
步驟 5 說明了交易性批次更新與忽略使用交易的批次更新的效果。 在接下來的三個教學課程中,我們將在本教學課程奠定的基礎上建立使用者介面,以執行批次更新、刪除和插入。
祝您程式設計愉快!
深入閱讀
有關本教學課程中討論的主題的更多資訊,請參閱以下資源:
關於作者
Scott Mitchell,七本 ASP/ASP.NET 書籍的作者和 4GuysFromRolla.com 創始人,自 1998 年以來便開始使用 Microsoft Web 技術。 Scott 擔任獨立顧問、講師和作家。 他的新書是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 您可以透過 mitchell@4GuysFromRolla.com 或他的部落格 (可以在 http://ScottOnWriting.NET 找到) 與他聯繫。
特別感謝
本教學課程系列已經過許多熱心的檢閱者檢閱。 本教學課程的主要審閱者是 Dave Gardner、Hilton Giesenow 和 Teresa Murphy。 有興趣查看我即將發表的 MSDN 文章嗎? 如果是這樣,請留言給我 mitchell@4GuysFromRolla.com。