導(dǎo)言:
正如我們?cè)诘?6章《概述插入、更新和刪除數(shù)據(jù)》里探討的那樣,GridView控件內(nèi)建的功能支持對(duì)每行數(shù)據(jù)的編輯和刪除功能,你只需要稍稍動(dòng)一下鼠標(biāo)就可以創(chuàng)建豐富的數(shù)據(jù)修改界面而不用寫一行代碼.但是,在某些情況下,這還不夠,我們需要讓用戶能夠成批地處理數(shù)據(jù).
比如,很多基于web(web-based)的電子郵件客戶端,將所有郵件出來,每條郵件除了包含郵件信息(主題、發(fā)送者等)外,還包含一個(gè)checkbox控件。這些界面允許用戶同時(shí)刪除多個(gè)郵件,用戶只需要選中郵件,再點(diǎn)"刪除所選郵件"按鈕.當(dāng)用戶要編輯多條不同的記錄的時(shí)候,提供一個(gè)批編輯界面是比較理想的.我們用不著讓用戶每次都選中一條要編輯的記錄,再做相關(guān)的修改,最后點(diǎn)“更新”按鈕,在批編輯界面里每條記錄都有各自的編輯選項(xiàng),用戶可以快速地編輯多條記錄再點(diǎn)“Update All”按鈕來保存對(duì)他們所做的修改.本系列我們將考察如何創(chuàng)建對(duì)數(shù)據(jù)進(jìn)行添加、編輯、刪除批處理的界面.
如果想對(duì)批處理執(zhí)行atomic operation(原子操作), 那么首先,所做的操作要么都執(zhí)行成功要么都失敗,另外還要對(duì)數(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í),原子數(shù)(atomicity)并非總數(shù)必要的。在批處理的某些情況下,某些修改成功某些修改失敗是可以接受的。比如刪除電子郵件時(shí),有些郵件在刪除過程中發(fā)生了數(shù)據(jù)庫錯(cuò)誤,有些郵件沒有發(fā)生錯(cuò)誤,對(duì)這種沒有發(fā)生錯(cuò)誤的郵件,批處理照樣將其刪除掉.對(duì)這種情況,我們沒有必要設(shè)置數(shù)據(jù)訪問層DAL支持?jǐn)?shù)據(jù)庫事務(wù).不過在其它某些情況下,原子數(shù)是至關(guān)重要的.比如某個(gè)客戶想把資金從一個(gè)銀行帳戶轉(zhuǎn)移到另一個(gè)銀行帳號(hào),下面2個(gè)操作必須執(zhí)行成功:首先,將第一個(gè)帳號(hào)的資金扣除,然后將資金轉(zhuǎn)入第二個(gè)帳號(hào).如果第一步執(zhí)行成功,第二步執(zhí)行失敗,銀行當(dāng)然高興,客戶怕是要發(fā)瘋了.在后面的文章里我們將創(chuàng)建添加、更新、刪除的批處理界面,就算你不打算在這些頁面里使用數(shù)據(jù)庫事務(wù),我也希望你照著本篇文章,對(duì)數(shù)據(jù)訪問層進(jìn)行擴(kuò)展一支持?jǐn)?shù)據(jù)庫事務(wù).
事務(wù)概述
絕大多數(shù)的數(shù)據(jù)庫都支持事務(wù),它可以將多個(gè)數(shù)據(jù)庫命令當(dāng)成一個(gè)邏輯單位進(jìn)行處理.這些包含事務(wù)的命令要么都執(zhí)行成功要么都執(zhí)行失敗.
一般來說,事務(wù)通過SQL命令來執(zhí)行,使用如下的模式:
1.聲明事務(wù)開始
2.執(zhí)行構(gòu)成事務(wù)的那些SQL命令
3.如果在第二步中的任何一個(gè)命令出錯(cuò),執(zhí)行事務(wù)回滾(rollback the transaction)
4.如果在第二步中的所有命令成功執(zhí)行,提交事務(wù)
這些SQL命令可以通過手寫的方式輸入,比如寫SQL腳本、創(chuàng)建存儲(chǔ)過程、也可以通過編程的方式來構(gòu)建,比如使用ADO.NET技術(shù)或調(diào)用System.Transactions namespace命名空間的類.在本文,我們僅僅考察用ADO.NET技術(shù)管理事務(wù).在后面的教程我們看如何在數(shù)據(jù)訪問層Data Access Layer里使用存儲(chǔ)過程,到那時(shí),我們?cè)賮砜疾爝@些創(chuàng)建、回滾、提交事物的SQL命令。另外,要獲得更多信息請(qǐng)參考文章《Managing Transactions in SQL Server Stored Procedures》(http://www.4guysfromrolla.com/webtech/080305-1.shtml)
注意:System.Transactions namespace命名空間的TransactionScope class類允許開發(fā)者通過編程的方式獲取事務(wù)里的一系列命令,且允許事務(wù)包含多個(gè)數(shù)據(jù)源,甚至類型不同,比如:Microsoft SQL Server database, 或Oracle database,甚至Web service.本教程我們使用ADO.NET技術(shù)而非TransactionScope class類,是因?yàn)锳DO.NET指定數(shù)據(jù)庫事務(wù)更詳細(xì),且在很多情況下占用資源更少.此外,在某些情況下,TransactionScope class類要用到Microsoft Distributed Transaction Coordinator (MSDTC),圍繞MSDTC的配置、執(zhí)行和性能問題是比較專業(yè)、高級(jí)的問題稍微超出了本教程的范圍.
在ADO.NET里,通過調(diào)用SqlConnection class類的BeginTransaction method方法啟動(dòng)事務(wù), 該方法返回一個(gè)SqlTransaction object對(duì)象.將構(gòu)成事務(wù)的數(shù)據(jù)操作命令放在try...catch區(qū)域,如果在try區(qū)域的某個(gè)命令出錯(cuò)的話,程序?qū)⑥D(zhuǎn)到catch區(qū)域,在此,通過SqlTransaction object對(duì)象的Rollback method方法執(zhí)行事務(wù)回滾。如果所有的命令執(zhí)行成功,將調(diào)用位于try區(qū)域底部的SqlTransaction object對(duì)象的Commit method方法來提交事務(wù).下面的代碼片段揭示了該模式。要想看在ADO.NET里使用事務(wù)的更多例子,請(qǐng)參閱文章《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ù)。為此,我們要對(duì)TableAdapter classes類進(jìn)行擴(kuò)展,以包含額外的方法以使用上述模式來執(zhí)行事務(wù)。在第二步,我們看如何使用一個(gè)partial classes類來添加這些方法.
第一步:創(chuàng)建批處理數(shù)據(jù)的頁面
在我們考察如何擴(kuò)展數(shù)據(jù)訪問層DAL以支持?jǐn)?shù)據(jù)庫事務(wù)之前,讓我們花點(diǎn)時(shí)間來創(chuàng)建一些ASP.NET web頁面,我們?cè)诒菊录昂竺嫒聦⒂玫剿鼈?
添加一個(gè)名為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>
完成后,花幾分鐘在瀏覽器里登錄頁面,左面的菜單列出了本部分的各項(xiàng)
圖3:Site Map現(xiàn)在包含了本章節(jié)
第二步:更新數(shù)據(jù)訪問層以支持?jǐn)?shù)據(jù)庫事務(wù)
就像我們?cè)诘谝徽隆秳?chuàng)建一個(gè)數(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的改動(dòng)對(duì)數(shù)據(jù)庫做相應(yīng)的更新,等等.記得TableAdapters有2種更新數(shù)據(jù)的模式——Batch Update 和 DB-Direct.就Batch Update模式而言, TableAdapter可以傳入DataSet, DataTable, 或DataRows集,遍歷這些數(shù)據(jù)對(duì)要添加、修改、刪除的行執(zhí)行相應(yīng)的InsertCommand, UpdateCommand, or DeleteCommand方法。就DB-Direct模式而言,TableAdapter傳入的是那些需要進(jìn)行添加、更新、刪除操作的某條記錄的列的值,再使用這些值執(zhí)行相關(guān)的InsertCommand, UpdateCommand, 或DeleteCommand命令.
TableAdapter自動(dòng)生成的方法并不使用事務(wù).默認(rèn)狀態(tài)下,TableAdapter執(zhí)行的每一個(gè)insert, update, 或delete操作都看作是單獨(dú)的、互不相干的.假定在業(yè)務(wù)邏輯層BLL里使用DB-Direct模式來向數(shù)據(jù)庫添加十條記錄,代碼將分十次調(diào)用TableAdapter的Insert方法. 如果前5條記錄添加正常,而在添加第六條記錄時(shí)發(fā)生異常,前5條記錄仍然保存在數(shù)據(jù)庫.同樣的,用Batch Update模式來操作的話,效果亦然.
在某些情況下,我們想確保在進(jìn)行一系列的改動(dòng)時(shí)引入原子數(shù)(atomicity).為此,我們必須手動(dòng)擴(kuò)展TableAdapter,通過添加一些新的方法將InsertCommand, UpdateCommand, 和DeleteCommands命令置于事務(wù)之下.在第一章《創(chuàng)建一個(gè)數(shù)據(jù)訪問層》里,我們考察了使用部分類(partial classes)對(duì)強(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)建一個(gè)名為TransactionSupport的子文件夾,再在里面添加一個(gè)新類,名為ProductsTableAdapter.TransactionSupport.cs (見圖4).該類包含ProductsTableAdapter的使用事務(wù)的方法.
圖4:創(chuàng)建一個(gè)名為TransactionSupport的新文件夾并添加一個(gè)名為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類.我們注意到在文件的頂部有一個(gè)using System.Data.SqlClient聲明,這是因?yàn)門ableAdapter被設(shè)置為使用SqlClient provider,在其內(nèi)部使用一個(gè)SqlDataAdapter object對(duì)象來向數(shù)據(jù)庫發(fā)出命令.因此,我們需要使用SqlTransaction class類來啟動(dòng)事務(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對(duì)象.CommitTransaction()和 RollbackTransaction()方法在關(guān)閉內(nèi)部的Connection object對(duì)象前分別調(diào)用Transaction object對(duì)象的Commit 和 Rollback方法.
添加上述代碼后,我們將在ProductsDataTable 或業(yè)務(wù)邏輯層BLL里添加方法以執(zhí)行一系列的置于事務(wù)之下的命令. 下面的代碼在Batch Update pattern模式里使用一個(gè)事務(wù)來更新一個(gè)ProductsDataTable instance實(shí)例.它調(diào)用BeginTransaction method方法來啟動(dòng)一個(gè)事務(wù),然后用一個(gè)try...catch模塊來發(fā)布數(shù)據(jù)更改命令.如果調(diào)用Adapter object對(duì)象的Update方法出現(xiàn)異常,那么將轉(zhuǎn)到catch區(qū)域,對(duì)事務(wù)進(jìn)行回滾.記得執(zhí)行Batch Update pattern模式的Update方法將遍歷ProductsDataTable里的所有行(rows),執(zhí)行相應(yīng)的InsertCommand, UpdateCommand, 和DeleteCommands命令.如果這些命令中的其中一個(gè)出現(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()方法接受一個(gè)int類型的ListT>,也就是要?jiǎng)h除的ProductIDs.該方法通過調(diào)用BeginTransaction來啟動(dòng)事務(wù),然后在try模塊里對(duì)每一個(gè)ProductID值調(diào)用DB-Direct模式的Delete方法.如果任何一個(gè)對(duì)Delete的調(diào)用出錯(cuò),將轉(zhuǎn)到catch 模塊,事務(wù)將會(huì)回滾;如果所有對(duì)Delete的調(diào)用成功,那就提交事務(wù)。添加該方法給ProductsBLL class類.
public void DeleteProductsWithTransaction
(System.Collections.Generic.Listint> 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;
}
}
在多個(gè)TableAdapters應(yīng)用事務(wù)
到目前為止我們考察的是對(duì)ProductsTableAdapter里的多個(gè)命令采用原子操作.如果我們是對(duì)多個(gè)不同的數(shù)據(jù)庫表進(jìn)行改動(dòng),并對(duì)這些改動(dòng)執(zhí)行原子操作那又怎么辦呢?比如:當(dāng)刪除一個(gè)category時(shí),在刪除之前我們想把該種類對(duì)應(yīng)的products分配給其它的category.對(duì)這種2步操作——分配products和刪除category——應(yīng)該執(zhí)行原子操作.但是ProductsTableAdapter只包含修改Products表的方法;而CategoriesTableAdapter只包含修改Categories表的方法.那么怎樣使用一個(gè)包含這2個(gè)TableAdapters的事務(wù)呢?
其中一個(gè)辦法是向CategoriesTableAdapter添加一個(gè)名為DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)的方法.再定義一個(gè)方法來調(diào)用一個(gè)存儲(chǔ)過程,使用事務(wù)來達(dá)到分配products和刪除category的目的.我們將在后面考察在一個(gè)存儲(chǔ)過程里開始、提交和回滾事務(wù).
另一個(gè)方法是在數(shù)據(jù)訪問層里添加一個(gè)類,來包含DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)方法.該方法創(chuàng)建CategoriesTableAdapter 和 the ProductsTableAdapter的實(shí)例,并將這2個(gè)TableAdapters的Connection屬性設(shè)置為相同的SqlConnection實(shí)例。這樣,它們都將調(diào)用BeginTransaction來開啟事務(wù).然后在try...catch模塊里執(zhí)行分配products和刪除category的方法,最后提交或回滾事務(wù).
第四步:向業(yè)務(wù)邏輯層添加UpdateWithTransaction方法
在第三步我們向數(shù)據(jù)訪問層DAL里的ProductsTableAdapter添加了一個(gè)UpdateWithTransaction方法,我們將向業(yè)務(wù)邏輯層添加相應(yīng)的方法.雖然表現(xiàn)層可以直接向DAL調(diào)用UpdateWithTransaction方法,但是我們?cè)谶@里仍然將它們分隔開。
打開ProductsBLL class類,添加一個(gè)名為UpdateWithTransaction的方法,該方法僅僅簡單地調(diào)用對(duì)應(yīng)的DAL方法.現(xiàn)在ProductsBLL類里有2個(gè)方法:UpdateWithTransaction方法——我們才添加的;以及DeleteProductsWithTransaction——我們?cè)诘谌教砑拥?
public int UpdateWithTransaction(Northwind.ProductsDataTable products)
{
return Adapter.UpdateWithTransaction(products);
}
public void DeleteProductsWithTransaction
(System.Collections.Generic.Listint> 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屬性。這是因?yàn)槲覀儗⒅苯釉贏SP.NET頁面的后臺(tái)代碼里調(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ù)
為演示更新一批記錄時(shí)事務(wù)的作用,我們將創(chuàng)建一個(gè)用戶界面來將所有產(chǎn)品用一個(gè)GridView控件顯示出來,并包含一個(gè)Button Web控件。當(dāng)點(diǎn)擊該按鈕時(shí)為product重新賦值一個(gè)有效的CategoryID值。具體來說,對(duì)頭幾個(gè)products分配一個(gè)有效的CategoryID值;而剩下的分配一個(gè)無效的(non-existent)CategoryID值,當(dāng)我們?cè)噲D對(duì)這樣的一個(gè)product——其CategoryID值與現(xiàn)有的category的CategoryID不匹配——進(jìn)行更新時(shí),將違反外鍵約束,進(jìn)而拋出一個(gè)異常.在本文的示例里你將看到,在使用事務(wù)時(shí),當(dāng)違反外鍵約束拋出一個(gè)異常時(shí)將導(dǎo)致前面的正確分配CategoryID值的操作產(chǎn)生回滾.如果不使用事務(wù)的話,這些正確的操作將執(zhí)行成功.
首先,打開BatchData文件夾里的Transactions.aspx頁面,從工具箱拖一個(gè)GridView控件到頁面。設(shè)置其ID為Products,從其智能標(biāo)簽里將其綁定到一個(gè)名為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將自動(dòng)的添加BoundFields以及一個(gè)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個(gè)Button Web控件,設(shè)置第一個(gè)按鈕的Text屬性 為“Refresh Grid”;第二個(gè)按鈕的Text屬性為“Modify Categories (WITH TRANSACTION)”;第三個(gè)按鈕的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>
此時(shí),在Visual Studio的設(shè)計(jì)模式里,界面看起來和下面的截屏差不多:
圖7:頁面包含一個(gè)GridView控件和三個(gè)Button Web控件
為這3個(gè)按鈕的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控件.
第二個(gè)事件處理器對(duì)products的CategoryID屬性重新賦值,并調(diào)用BLL層里的新的事務(wù)方法來執(zhí)行數(shù)據(jù)庫更新.我們注意到將每個(gè)產(chǎn)品的ProductID值賦給其CategoryID屬性,對(duì)最開頭的幾個(gè)產(chǎn)品而言沒有任何問題,但隨著ProductID值越變?cè)酱?,CategoryID的值也越變?cè)酱?,而Category表里定義的種類畢竟有限,于是問題就出來了。
第三個(gè)事件處理器也是將ProductID值賦給CategoryID屬性,只是用ProductsTableAdapter的默認(rèn)的Update方法來更新數(shù)據(jù)庫. 該Update方法并沒有使用事務(wù)來封裝這些命令,所以只要是沒有違背外鍵約束的更新都會(huì)執(zhí)行成功.
在瀏覽器里登錄該頁面進(jìn)行驗(yàn)證.最開始你將看到如圖8所示的畫面,然后點(diǎn)“Modify Categories (WITH TRANSACTION)”.這將導(dǎo)致頁面回傳并試題更新所有products的CategoryID值,這將導(dǎo)致違背外鍵約束(見圖9).
圖8:Products將顯示在一個(gè)分頁的GridView控件里
圖9:導(dǎo)致違背外鍵約束
現(xiàn)在點(diǎn)擊瀏覽器的Back按鈕,再點(diǎn)擊“Refresh Grid”按鈕,此時(shí)你看到的界面和圖8的界面一摸一樣。這是因?yàn)榘l(fā)生了違背外鍵約束,導(dǎo)致回滾,所有的操作失敗.
再點(diǎn)“Modify Categories (WITHOUT TRANSACTION)”按鈕,這同樣將違背外鍵約束(見圖9),不過這一次,那些對(duì)CategoryID屬性賦以有效值的操作不會(huì)回滾.點(diǎn)擊瀏覽器的Back按鈕,再點(diǎn)“Refresh Grid”按鈕。就像圖10顯示的那樣,最開始的8個(gè)產(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ù)的方法.在本教程,我們?cè)赑roductsTableAdapter class類里創(chuàng)建了這3個(gè)方法:BeginTransaction, CommitTransaction,和RollbackTransaction.我們考察了如何在try...catch模塊里使用這些方法來執(zhí)行一系列的修改命令.具體來說,我們?cè)赑roductsTableAdapter里創(chuàng)建了UpdateWithTransaction方法,該方法運(yùn)用Batch Update模式對(duì)ProductsDataTable里的每行記錄執(zhí)行必要的更改操作;我們也對(duì)BLL里的ProductsBLL class類添加了DeleteProductsWithTransaction方法,它將一系列ProductID值作為輸入?yún)?shù),并使用DB-Direct模式將每個(gè)產(chǎn)品刪除.這些方法開始都創(chuàng)建一個(gè)事務(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ù)教程》,希望對(duì)大家的學(xué)習(xí)ASP.NET有所幫助。
您可能感興趣的文章:- 在ASP.NET 2.0中操作數(shù)據(jù)之五十九:使用SQL緩存依賴項(xiàng)SqlCacheDependency
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十:創(chuàng)建一個(gè)自定義的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)建新的存儲(chǔ)過程
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十六:在TableAdapters中使用現(xiàn)有的存儲(chǔ)過程
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十七:在TableAdapters中使用JOINs
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十八:為DataTable添加額外的列
- 在ASP.NET 2.0中操作數(shù)據(jù)之六十九:處理Computed Columns列