DataSource Control 與 Transaction的課題
文/黃忠成
Transaction一向是資料庫應用程式必須正視的課題,當使用ADO.NET時,可以透過Connection物件來啟動Trasnsaction,但來到了SqlDataSource控件時,又該如何啟動Transaction呢?這個問題並不容易回答,SqlDataSource控件的設計概念於更新時是以單筆資料為基礎,也就是說每次執行更新前會自動建立一個Connection物件,更新完成後就會Connection物件關閉並釋放掉,那這是否意味著我們無法使用SqlDataSource控件來處理Transaction呢?那麼這樣一來,更新出貨單時扣減庫存的動作不是就無法放在一個Transaction中了嗎?會有這個疑問是當然的,但是若細想後,你會查覺到前面ADO.NET一節中曾經提過的TransactionScope機制,運用這個機制是否能達到需求呢?讓我們試試吧,先在Northwind資料庫的Customers資料表中添加一個NOTES欄位,型態為varchar(50)或nvarchar(50)後,再建立一個WebSite專案,組態SqlDataSource控件連結至此資料表,於網頁上放入一個Button控件,然後在WebSite專案上按右鍵,新增對System.Transaction的參考,於Button的Click事件中鍵入4-5-13的程式碼。
程式4-5-13
using System; using System.Data; using System.Configuration; using System.Collections; 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; using System.Transactions;
public partial class UpdateWithTransaction : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) {
} protected void Button1_Click(object sender, EventArgs e) { using (TransactionScope scope = new TransactionScope()) { DataView dv = (DataView)SqlDataSource1.Select(DataSourceSelectArguments.Empty); foreach (DataRowView rv in dv) { foreach (DataColumn col in rv.Row.Table.Columns) SqlDataSource1.UpdateParameters[col.ColumnName].DefaultValue = rv[col.ColumnName].ToString(); SqlDataSource1.UpdateParameters["NOTES"].DefaultValue = "TEST11111"; SqlDataSource1.Update(); } scope.Complete(); } } } |
執行此網頁按下Button後,再到資料庫查詢Customers資料表,你會發現所有NOTES欄位都已經被改變了。
圖4-5-37
請下達SQL指令,再次將NOTES清空。
圖4-5-38
然後移除程式4-5-13中的scope.Complete函式的呼叫程式碼,再次執行後,你會發現,NOTES值並未被改變。
圖4-5-39
這意味著,使用SqlDataSource控件搭配TransactionScope後,便可於SqlDataSource控件中使用交易。當然!有些人可能不是很喜歡TransactionScope這種隱誨式交易的寫法(我是其中之一,況且TransactionScope升級成DTC的問題,會嚴重影響程式的效能),而比較喜歡使用舊有的Transaction機制,這也是可以辦到的,只要運用Updating、Deleting、Inserting等事件即可,見程式4-5-14。
程式4-5-14
using System; using System.Data; using System.Data.Common; using System.Data.SqlClient; using System.Configuration; using System.Collections; 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;
public partial class UpdateWithTrasnaction2 : System.Web.UI.Page { private SqlConnection _tempConn = null; private SqlTransaction _trans = null;
protected void Page_Load(object sender, EventArgs e) {
} protected void Button1_Click(object sender, EventArgs e) { _tempConn = new SqlConnection( _tempConn.Open(); _trans = _tempConn.BeginTransaction(); try { DataView dv = (DataView)SqlDataSource1.Select(DataSourceSelectArguments.Empty); foreach (DataRowView rv in dv) { foreach (DataColumn col in rv.Row.Table.Columns) SqlDataSource1.UpdateParameters[col.ColumnName].DefaultValue = rv[col.ColumnName].ToString(); SqlDataSource1.UpdateParameters["NOTES"].DefaultValue = "TEST11111"; SqlDataSource1.Update(); } // _trans.Commit(); _trans.Rollback(); } catch (Exception) { _trans.Rollback(); } _tempConn.Close(); }
private void ReplaceNullValues(DbCommand command) { int count = command.Parameters.Count; foreach (DbParameter parameter in command.Parameters) { if(parameter.Value == null) parameter.Value = DBNull.Value; } }
protected void SqlDataSource1_Updating(object sender, SqlDataSourceCommandEventArgs e) { e.Command.Connection = _tempConn; e.Command.Transaction = _trans; ReplaceNullValues(e.Command); if (e.Command.ExecuteNonQuery() > 1) throw new Exception("The Command affected more then one rows."); e.Cancel = true; } } |
將_trans.Rollback改為_trans.Complete後,便可正確將資料寫入資料庫,反之則是取消交易。不過說實話,如非必要,使用ObjectDataSource+TableAdapter、或直接操作ADO.NET來處理這種情況,會是較正確的選擇。