在ASP.NET 2.0中操作數(shù)據(jù)之六十一:在事務(wù)里對數(shù)據(jù)庫修改進(jìn)行封裝
導(dǎo)言:
正如我們在第16章《概述插入、更新和刪除數(shù)據(jù)》里探討的那樣,GridView控件內(nèi)建的功能支持對每行數(shù)據(jù)的編輯和刪除功能,你只需要稍稍動一下鼠標(biāo)就可以創(chuàng)建豐富的數(shù)據(jù)修改界面而不用寫一行代碼.但是,在某些情況下,這還不夠,我們需要讓用戶能夠成批地處理數(shù)據(jù).
比如,很多基于web(web-based)的電子郵件客戶端,將所有郵件出來,每條郵件除了包含郵件信息(主題、發(fā)送者等)外,還包含一個checkbox控件。這些界面允許用戶同時刪除多個郵件,用戶只需要選中郵件,再點(diǎn)"刪除所選郵件"按鈕.當(dāng)用戶要編輯多條不同的記錄的時候,提供一個批編輯界面是比較理想的.我們用不著讓用戶每次都選中一條要編輯的記錄,再做相關(guān)的修改,最后點(diǎn)“更新”按鈕,在批編輯界面里每條記錄都有各自的編輯選項,用戶可以快速地編輯多條記錄再點(diǎn)“Update All”按鈕來保存對他們所做的修改.本系列我們將考察如何創(chuàng)建對數(shù)據(jù)進(jìn)行添加、編輯、刪除批處理的界面.
如果想對批處理執(zhí)行atomic operation(原子操作), 那么首先,所做的操作要么都執(zhí)行成功要么都失敗,另外還要對數(shù)據(jù)訪問層進(jìn)行擴(kuò)充以支持database transactions(數(shù)據(jù)庫事務(wù))。數(shù)據(jù)庫事務(wù)確保INSERT, UPDATE, 和 DELETE語句執(zhí)行的atomicity(原子數(shù))置于數(shù)據(jù)庫事務(wù)的保護(hù)之下.另外,絕大多數(shù)的當(dāng)代數(shù)據(jù)庫系統(tǒng)都支持?jǐn)?shù)據(jù)庫事務(wù).
在本系列我們先看如何擴(kuò)充數(shù)據(jù)訪問層以支持?jǐn)?shù)據(jù)庫事務(wù),接下來我們看如何創(chuàng)建頁面以包含添加、更新、刪除數(shù)據(jù)的批處理界面,讓我們開始吧.
注意:在批處理事務(wù)里修改數(shù)據(jù)時,原子數(shù)(atomicity)并非總數(shù)必要的。在批處理的某些情況下,某些修改成功某些修改失敗是可以接受的。比如刪除電子郵件時,有些郵件在刪除過程中發(fā)生了數(shù)據(jù)庫錯誤,有些郵件沒有發(fā)生錯誤,對這種沒有發(fā)生錯誤的郵件,批處理照樣將其刪除掉.對這種情況,我們沒有必要設(shè)置數(shù)據(jù)訪問層DAL支持?jǐn)?shù)據(jù)庫事務(wù).不過在其它某些情況下,原子數(shù)是至關(guān)重要的.比如某個客戶想把資金從一個銀行帳戶轉(zhuǎn)移到另一個銀行帳號,下面2個操作必須執(zhí)行成功:首先,將第一個帳號的資金扣除,然后將資金轉(zhuǎn)入第二個帳號.如果第一步執(zhí)行成功,第二步執(zhí)行失敗,銀行當(dāng)然高興,客戶怕是要發(fā)瘋了.在后面的文章里我們將創(chuàng)建添加、更新、刪除的批處理界面,就算你不打算在這些頁面里使用數(shù)據(jù)庫事務(wù),我也希望你照著本篇文章,對數(shù)據(jù)訪問層進(jìn)行擴(kuò)展一支持?jǐn)?shù)據(jù)庫事務(wù).
事務(wù)概述
絕大多數(shù)的數(shù)據(jù)庫都支持事務(wù),它可以將多個數(shù)據(jù)庫命令當(dāng)成一個邏輯單位進(jìn)行處理.這些包含事務(wù)的命令要么都執(zhí)行成功要么都執(zhí)行失敗.
一般來說,事務(wù)通過SQL命令來執(zhí)行,使用如下的模式:
1.聲明事務(wù)開始
2.執(zhí)行構(gòu)成事務(wù)的那些SQL命令
3.如果在第二步中的任何一個命令出錯,執(zhí)行事務(wù)回滾(rollback the transaction)
4.如果在第二步中的所有命令成功執(zhí)行,提交事務(wù)
這些SQL命令可以通過手寫的方式輸入,比如寫SQL腳本、創(chuàng)建存儲過程、也可以通過編程的方式來構(gòu)建,比如使用ADO.NET技術(shù)或調(diào)用System.Transactions namespace命名空間的類.在本文,我們僅僅考察用ADO.NET技術(shù)管理事務(wù).在后面的教程我們看如何在數(shù)據(jù)訪問層Data Access Layer里使用存儲過程,到那時,我們再來考察這些創(chuàng)建、回滾、提交事物的SQL命令。另外,要獲得更多信息請參考文章《Managing Transactions in SQL Server Stored Procedures》(http://www.4guysfromrolla.com/webtech/080305-1.shtml)
注意:System.Transactions namespace命名空間的TransactionScope class類允許開發(fā)者通過編程的方式獲取事務(wù)里的一系列命令,且允許事務(wù)包含多個數(shù)據(jù)源,甚至類型不同,比如:Microsoft SQL Server database, 或Oracle database,甚至Web service.本教程我們使用ADO.NET技術(shù)而非TransactionScope class類,是因為ADO.NET指定數(shù)據(jù)庫事務(wù)更詳細(xì),且在很多情況下占用資源更少.此外,在某些情況下,TransactionScope class類要用到Microsoft Distributed Transaction Coordinator (MSDTC),圍繞MSDTC的配置、執(zhí)行和性能問題是比較專業(yè)、高級的問題稍微超出了本教程的范圍.
在ADO.NET里,通過調(diào)用SqlConnection class類的BeginTransaction method方法啟動事務(wù), 該方法返回一個SqlTransaction object對象.將構(gòu)成事務(wù)的數(shù)據(jù)操作命令放在try...catch區(qū)域,如果在try區(qū)域的某個命令出錯的話,程序?qū)⑥D(zhuǎn)到catch區(qū)域,在此,通過SqlTransaction object對象的Rollback method方法執(zhí)行事務(wù)回滾。如果所有的命令執(zhí)行成功,將調(diào)用位于try區(qū)域底部的SqlTransaction object對象的Commit method方法來提交事務(wù).下面的代碼片段揭示了該模式。要想看在ADO.NET里使用事務(wù)的更多例子,請參閱文章《Maintaining Database Consistency with Transactions》(http://aspnet.4guysfromrolla.com/articles/072705-1.aspx).
// 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; }
默認(rèn)情況下,強(qiáng)類型數(shù)據(jù)集(Typed DataSet)里的TableAdapters并不使用事務(wù)。為此,我們要對TableAdapter classes類進(jìn)行擴(kuò)展,以包含額外的方法以使用上述模式來執(zhí)行事務(wù)。在第二步,我們看如何使用一個partial classes類來添加這些方法.
第一步:創(chuàng)建批處理數(shù)據(jù)的頁面
在我們考察如何擴(kuò)展數(shù)據(jù)訪問層DAL以支持?jǐn)?shù)據(jù)庫事務(wù)之前,讓我們花點(diǎn)時間來創(chuàng)建一些ASP.NET web頁面,我們在本章及后面三章將用到它們.
添加一個名為BatchData的新文件夾,再添加如下的 ASP.NET頁面, 務(wù)必套用Site.master模板頁.
Default.aspx
Transactions.aspx
BatchUpdate.aspx
BatchDelete.aspx
BatchInsert.aspx
圖1:添加相關(guān)的頁面
就像其它文件夾里的Default.aspx頁面一樣,用SectionLevelTutorialListing.ascx用戶控件來列出本部分的章節(jié)。將其從解決資源管理器里拖到Default.aspx頁面.
圖2:將SectionLevelTutorialListing.ascx用戶控件添加到Default.aspx頁面
最后添加如下代碼到Web.sitemap文件,具體的,將其添加到“Customizing the Site Map” <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>
完成后,花幾分鐘在瀏覽器里登錄頁面,左面的菜單列出了本部分的各項
圖3:Site Map現(xiàn)在包含了本章節(jié)
第二步:更新數(shù)據(jù)訪問層以支持?jǐn)?shù)據(jù)庫事務(wù)
就像我們在第一章《創(chuàng)建一個數(shù)據(jù)訪問層》探討的一樣,位于數(shù)據(jù)訪問層的強(qiáng)類型數(shù)據(jù)集(Typed DataSet)由DataTables 和 TableAdapters構(gòu)成. DataTables保存數(shù)據(jù),而TableAdapters提供相應(yīng)的方法從數(shù)據(jù)庫讀取數(shù)據(jù),并根據(jù)DataTables的改動對數(shù)據(jù)庫做相應(yīng)的更新,等等.記得TableAdapters有2種更新數(shù)據(jù)的模式——Batch Update 和 DB-Direct.就Batch Update模式而言, TableAdapter可以傳入DataSet, DataTable, 或DataRows集,遍歷這些數(shù)據(jù)對要添加、修改、刪除的行執(zhí)行相應(yīng)的InsertCommand, UpdateCommand, or DeleteCommand方法。就DB-Direct模式而言,TableAdapter傳入的是那些需要進(jìn)行添加、更新、刪除操作的某條記錄的列的值,再使用這些值執(zhí)行相關(guān)的InsertCommand, UpdateCommand, 或DeleteCommand命令.
TableAdapter自動生成的方法并不使用事務(wù).默認(rèn)狀態(tài)下,TableAdapter執(zhí)行的每一個insert, update, 或delete操作都看作是單獨(dú)的、互不相干的.假定在業(yè)務(wù)邏輯層BLL里使用DB-Direct模式來向數(shù)據(jù)庫添加十條記錄,代碼將分十次調(diào)用TableAdapter的Insert方法. 如果前5條記錄添加正常,而在添加第六條記錄時發(fā)生異常,前5條記錄仍然保存在數(shù)據(jù)庫.同樣的,用Batch Update模式來操作的話,效果亦然.
在某些情況下,我們想確保在進(jìn)行一系列的改動時引入原子數(shù)(atomicity).為此,我們必須手動擴(kuò)展TableAdapter,通過添加一些新的方法將InsertCommand, UpdateCommand, 和DeleteCommands命令置于事務(wù)之下.在第一章《創(chuàng)建一個數(shù)據(jù)訪問層》里,我們考察了使用部分類(partial classes)對強(qiáng)類型數(shù)據(jù)集(Typed DataSet)里的DataTable的函數(shù)進(jìn)行擴(kuò)充.該技術(shù)同樣適用于TableAdapter.
強(qiáng)類型數(shù)據(jù)集Northwind.xsd位于App_Code文件夾的DAL子文件夾里.在DAL文件夾里再創(chuàng)建一個名為TransactionSupport的子文件夾,再在里面添加一個新類,名為ProductsTableAdapter.TransactionSupport.cs (見圖4).該類包含ProductsTableAdapter的使用事務(wù)的方法.
圖4:創(chuàng)建一個名為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(); } } }
類聲明里的關(guān)鍵字partial向編譯器表明代碼里添加的成員(members)是添加到命名空間NorthwindTableAdapters里的ProductsTableAdapter class類.我們注意到在文件的頂部有一個using System.Data.SqlClient聲明,這是因為TableAdapter被設(shè)置為使用SqlClient provider,在其內(nèi)部使用一個SqlDataAdapter object對象來向數(shù)據(jù)庫發(fā)出命令.因此,我們需要使用SqlTransaction class類來啟動事務(wù),然后提交或回滾事務(wù).如果沒有使用Microsoft SQL Server數(shù)據(jù)庫的話,你需要調(diào)用恰當(dāng)?shù)膒rovider.
這些方法被標(biāo)記為public,我們可以在ProductsTableAdapter里,或數(shù)據(jù)訪問層DAL的其它類,甚至是其它層比如業(yè)務(wù)邏輯層BLL來調(diào)用這些法.
BeginTransaction()方法打開了TableAdapter的內(nèi)部的SqlConnection(如果需要的話), 開啟事務(wù)并賦值給Transaction屬性,并將事務(wù)分配(attache)給SqlDataAdapter的SqlCommand objects對象.CommitTransaction()和 RollbackTransaction()方法在關(guān)閉內(nèi)部的Connection object對象前分別調(diào)用Transaction object對象的Commit 和 Rollback方法.
添加上述代碼后,我們將在ProductsDataTable 或業(yè)務(wù)邏輯層BLL里添加方法以執(zhí)行一系列的置于事務(wù)之下的命令. 下面的代碼在Batch Update pattern模式里使用一個事務(wù)來更新一個ProductsDataTable instance實(shí)例.它調(diào)用BeginTransaction method方法來啟動一個事務(wù),然后用一個try...catch模塊來發(fā)布數(shù)據(jù)更改命令.如果調(diào)用Adapter object對象的Update方法出現(xiàn)異常,那么將轉(zhuǎn)到catch區(qū)域,對事務(wù)進(jìn)行回滾.記得執(zhí)行Batch Update pattern模式的Update方法將遍歷ProductsDataTable里的所有行(rows),執(zhí)行相應(yīng)的InsertCommand, UpdateCommand, 和DeleteCommands命令.如果這些命令中的其中一個出現(xiàn)異常,事務(wù)將回滾,撤銷在事務(wù)里的所做的更改.如果Update命令全部執(zhí)行無異常,那么提交事務(wù).
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; } }
將上述的UpdateWithTransaction()方法添加到文件ProductsTableAdapter.TransactionSupport.cs里的ProductsTableAdapter class類。另外,還可以將該方法添加到業(yè)務(wù)邏輯層的ProductsBLL class類,不過要做些許修改:即將this.BeginTransaction(), this.CommitTransaction(), and this.RollbackTransaction()三中方法里的關(guān)鍵字“this”替換為“Adapter”(我們知道,ProductsBLL類里的ProductsTableAdapter的name屬性即是Adapter).
UpdateWithTransaction()方法使用的是Batch Update模式,不過也可在事務(wù)里調(diào)用DB-Direct模式,就像下面的代碼顯示的那樣.DeleteProductsWithTransaction()方法接受一個int類型的List<T>,也就是要刪除的ProductIDs.該方法通過調(diào)用BeginTransaction來啟動事務(wù),然后在try模塊里對每一個ProductID值調(diào)用DB-Direct模式的Delete方法.如果任何一個對Delete的調(diào)用出錯,將轉(zhuǎn)到catch 模塊,事務(wù)將會回滾;如果所有對Delete的調(diào)用成功,那就提交事務(wù)。添加該方法給ProductsBLL class類.
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; } }
在多個TableAdapters應(yīng)用事務(wù)
到目前為止我們考察的是對ProductsTableAdapter里的多個命令采用原子操作.如果我們是對多個不同的數(shù)據(jù)庫表進(jìn)行改動,并對這些改動執(zhí)行原子操作那又怎么辦呢?比如:當(dāng)刪除一個category時,在刪除之前我們想把該種類對應(yīng)的products分配給其它的category.對這種2步操作——分配products和刪除category——應(yīng)該執(zhí)行原子操作.但是ProductsTableAdapter只包含修改Products表的方法;而CategoriesTableAdapter只包含修改Categories表的方法.那么怎樣使用一個包含這2個TableAdapters的事務(wù)呢?
其中一個辦法是向CategoriesTableAdapter添加一個名為DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)的方法.再定義一個方法來調(diào)用一個存儲過程,使用事務(wù)來達(dá)到分配products和刪除category的目的.我們將在后面考察在一個存儲過程里開始、提交和回滾事務(wù).
另一個方法是在數(shù)據(jù)訪問層里添加一個類,來包含DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)方法.該方法創(chuàng)建CategoriesTableAdapter 和 the ProductsTableAdapter的實(shí)例,并將這2個TableAdapters的Connection屬性設(shè)置為相同的SqlConnection實(shí)例。這樣,它們都將調(diào)用BeginTransaction來開啟事務(wù).然后在try...catch模塊里執(zhí)行分配products和刪除category的方法,最后提交或回滾事務(wù).
第四步:向業(yè)務(wù)邏輯層添加UpdateWithTransaction方法
在第三步我們向數(shù)據(jù)訪問層DAL里的ProductsTableAdapter添加了一個UpdateWithTransaction方法,我們將向業(yè)務(wù)邏輯層添加相應(yīng)的方法.雖然表現(xiàn)層可以直接向DAL調(diào)用UpdateWithTransaction方法,但是我們在這里仍然將它們分隔開。
打開ProductsBLL class類,添加一個名為UpdateWithTransaction的方法,該方法僅僅簡單地調(diào)用對應(yīng)的DAL方法.現(xiàn)在ProductsBLL類里有2個方法:UpdateWithTransaction方法——我們才添加的;以及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頁面的后臺代碼里調(diào)用這些方法,記得DataObjectMethodAttribute方法的作用是指出哪些方法應(yīng)該出現(xiàn)在ObjectDataSource控件的設(shè)置數(shù)據(jù)源向?qū)У哪承?biāo)簽(SELECT, UPDATE, INSERT, 或DELETE)里.由于GridView控件缺乏內(nèi)置的支持“批編輯”或“批刪除”的功能,我們將通過編輯的方式來調(diào)用這些方法.
第五步:在表現(xiàn)層更新數(shù)據(jù)庫數(shù)據(jù)
為演示更新一批記錄時事務(wù)的作用,我們將創(chuàng)建一個用戶界面來將所有產(chǎn)品用一個GridView控件顯示出來,并包含一個Button Web控件。當(dāng)點(diǎn)擊該按鈕時為product重新賦值一個有效的CategoryID值。具體來說,對頭幾個products分配一個有效的CategoryID值;而剩下的分配一個無效的(non-existent)CategoryID值,當(dāng)我們試圖對這樣的一個product——其CategoryID值與現(xiàn)有的category的CategoryID不匹配——進(jìn)行更新時,將違反外鍵約束,進(jìn)而拋出一個異常.在本文的示例里你將看到,在使用事務(wù)時,當(dāng)違反外鍵約束拋出一個異常時將導(dǎo)致前面的正確分配CategoryID值的操作產(chǎn)生回滾.如果不使用事務(wù)的話,這些正確的操作將執(zhí)行成功.
首先,打開BatchData文件夾里的Transactions.aspx頁面,從工具箱拖一個GridView控件到頁面。設(shè)置其ID為Products,從其智能標(biāo)簽里將其綁定到一個名為ProductsDataSource的ObjectDataSource控件,設(shè)置該控件調(diào)用ProductsBLL class類的GetProducts()方法。由于該GridView是“只讀”的,在UPDATE, INSERT, 和DELETE標(biāo)簽里選“(None)”,點(diǎn)完成。
圖5:設(shè)置ObjectDataSource使用ProductsBLL Class類的GetProducts方法
圖6:在UPDATE, INSERT, 和DELETE標(biāo)簽里選“(None)”
完成設(shè)置后,Visual Studio將自動的添加BoundFields以及一個CheckBoxField,刪除ProductID, ProductName, CategoryID,和CategoryName以外的其它列;并且分別將ProductName 和 CategoryName列的HeaderText屬性重命名為“Product” 和 “Category”.在智能標(biāo)簽里啟用“分頁”功能.做完這些修改后,GridView 和 ObjectDataSource控件的聲明代碼看起來應(yīng)該和下面的差不多:
<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控件,設(shè)置第一個按鈕的Text屬性 為“Refresh Grid”;第二個按鈕的Text屬性為“Modify Categories (WITH TRANSACTION)”;第三個按鈕的Text屬性為“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的設(shè)計模式里,界面看起來和下面的截屏差不多:
圖7:頁面包含一個GridView控件和三個Button Web控件
為這3個按鈕的Click events事件創(chuàng)建事件處理器,如下:
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(); }
refresh按鈕的Click事件處理器僅僅調(diào)用Products GridView的DataBind方法將數(shù)據(jù)重新綁定到ridView控件.
第二個事件處理器對products的CategoryID屬性重新賦值,并調(diào)用BLL層里的新的事務(wù)方法來執(zhí)行數(shù)據(jù)庫更新.我們注意到將每個產(chǎn)品的ProductID值賦給其CategoryID屬性,對最開頭的幾個產(chǎn)品而言沒有任何問題,但隨著ProductID值越變越大,CategoryID的值也越變越大,而Category表里定義的種類畢竟有限,于是問題就出來了。
第三個事件處理器也是將ProductID值賦給CategoryID屬性,只是用ProductsTableAdapter的默認(rèn)的Update方法來更新數(shù)據(jù)庫. 該Update方法并沒有使用事務(wù)來封裝這些命令,所以只要是沒有違背外鍵約束的更新都會執(zhí)行成功.
在瀏覽器里登錄該頁面進(jìn)行驗證.最開始你將看到如圖8所示的畫面,然后點(diǎn)“Modify Categories (WITH TRANSACTION)”.這將導(dǎo)致頁面回傳并試題更新所有products的CategoryID值,這將導(dǎo)致違背外鍵約束(見圖9).
圖8:Products將顯示在一個分頁的GridView控件里
圖9:導(dǎo)致違背外鍵約束
現(xiàn)在點(diǎn)擊瀏覽器的Back按鈕,再點(diǎn)擊“Refresh Grid”按鈕,此時你看到的界面和圖8的界面一摸一樣。這是因為發(fā)生了違背外鍵約束,導(dǎo)致回滾,所有的操作失敗.
再點(diǎn)“Modify Categories (WITHOUT TRANSACTION)”按鈕,這同樣將違背外鍵約束(見圖9),不過這一次,那些對CategoryID屬性賦以有效值的操作不會回滾.點(diǎn)擊瀏覽器的Back按鈕,再點(diǎn)“Refresh Grid”按鈕。就像圖10顯示的那樣,最開始的8個產(chǎn)品的CategoryID值已經(jīng)發(fā)生了更改,比如,在圖8里Chang的CategoryID值為1,而在圖10里就變成了2了.
圖10:某些Product的CategoryID值發(fā)生了改變,而其它的沒有
結(jié)語:
默認(rèn)情況下,TableAdapter的方法沒有使用事務(wù)來執(zhí)行數(shù)據(jù)庫命令,不過只需多做一點(diǎn)工作我們就可以添加一些用于創(chuàng)建、提交、回滾事務(wù)的方法.在本教程,我們在ProductsTableAdapter class類里創(chuàng)建了這3個方法:BeginTransaction, CommitTransaction,和RollbackTransaction.我們考察了如何在try...catch模塊里使用這些方法來執(zhí)行一系列的修改命令.具體來說,我們在ProductsTableAdapter里創(chuàng)建了UpdateWithTransaction方法,該方法運(yùn)用Batch Update模式對ProductsDataTable里的每行記錄執(zhí)行必要的更改操作;我們也對BLL里的ProductsBLL class類添加了DeleteProductsWithTransaction方法,它將一系列ProductID值作為輸入?yún)?shù),并使用DB-Direct模式將每個產(chǎn)品刪除.這些方法開始都創(chuàng)建一個事務(wù),再在try...catch模塊里執(zhí)行數(shù)據(jù)更改命令.如果拋出異常,則回滾事務(wù),否則提交事務(wù).
第五步演示了事務(wù)的作用。在接下來的3章我們將以本章為基礎(chǔ),創(chuàng)建批更新、批刪除、批添加的用戶界面.
祝編程快樂!
作者簡介
本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創(chuàng)始人,自1998年以來一直應(yīng)用 微軟Web技術(shù)。大家可以點(diǎn)擊查看全部教程《[翻譯]Scott Mitchell 的ASP.NET 2.0數(shù)據(jù)教程》,希望對大家的學(xué)習(xí)ASP.NET有所幫助。
- 在ASP.NET 2.0中操作數(shù)據(jù)之五十九:使用SQL緩存依賴項SqlCacheDependency
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十:創(chuàng)建一個自定義的Database-Driven Site Map Provider
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十二:GridView批量更新數(shù)據(jù)
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十三:GridView實(shí)現(xiàn)批量刪除數(shù)據(jù)
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十四:GridView批量添加數(shù)據(jù)
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十五:在TableAdapters中創(chuàng)建新的存儲過程
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十六:在TableAdapters中使用現(xiàn)有的存儲過程
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十七:在TableAdapters中使用JOINs
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十八:為DataTable添加額外的列
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十九:處理Computed Columns列
相關(guān)文章
NopCommerce架構(gòu)分析之(五)Model綁定Action參數(shù)
本文主要介紹NopCommerce中在請求Controller的Action方法前,通過ModelBinder將Model進(jìn)行綁定,以便后續(xù)捕捉到數(shù)據(jù),轉(zhuǎn)化成對象,再進(jìn)行處理。2016-04-04在ASP.NET 2.0中操作數(shù)據(jù)之二十:定制數(shù)據(jù)修改界面
本文主要介紹如何對GridView的編輯界面進(jìn)行定制,使GridView在編輯時具有DropDownList和RadioButtonList控件,提供更人性化的界面。2016-05-05在ASP.NET 2.0中操作數(shù)據(jù)之三十一:使用DataList來一行顯示多條記錄
ASP.NET 2.0中DataList默認(rèn)情況使用單列多行的table來顯示項,本文介紹通過設(shè)置RepeatColumns屬性為每行的列數(shù)就可以達(dá)到顯示多條記錄這個目的。2016-05-05在ASP.NET 2.0中操作數(shù)據(jù)之三十五:使用Repeater和DataList單頁面實(shí)現(xiàn)主/從報表
前面已經(jīng)介紹了ASP.NET 2.0中如何使用兩個頁面實(shí)現(xiàn)主/從報表,本文主要講解,如何使用一個單獨(dú)頁面實(shí)現(xiàn)主/從報表。2016-05-05.Net?Core微服務(wù)網(wǎng)關(guān)Ocelot集成Consul
這篇文章介紹了.Net?Core微服務(wù)網(wǎng)關(guān)Ocelot集成Consul的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-01-01在ASP.NET 2.0中操作數(shù)據(jù)之五十三:在Data Web控件顯示二進(jìn)制數(shù)據(jù)
本文主要介紹在ASP.NET 2.0中直接顯示PDF超鏈接的方法,以及如何把已二進(jìn)制數(shù)據(jù)形式保存的圖片顯示在GridView中的方法,雖然這種方法在實(shí)際開發(fā)中很少用,但還是值得學(xué)習(xí)一下。2016-05-05在ASP.NET 2.0中操作數(shù)據(jù)之七十三:用Managed Code創(chuàng)建存儲過程和用戶自定義函數(shù)(上部分)
Microsoft SQL Server 2005整合了Common Language Runtime (CLR),它允許用managed code來創(chuàng)建數(shù)據(jù)庫對象,本文主要介紹了如何使用managed code創(chuàng)建存儲過程。2016-05-05在ASP.NET 2.0中操作數(shù)據(jù)之四十四:DataList和Repeater數(shù)據(jù)排序(三)
上篇已經(jīng)完成了自定義分頁,這一節(jié)我們繼續(xù)完善排序功能。2016-05-05在ASP.NET 2.0中操作數(shù)據(jù)之三十八:處理BLL和DAL的異常
本文主要介紹如何在BLL和DAL層如何處理異常,以達(dá)到給用戶顯示友好的錯誤信息。2016-05-05