所有寫數據庫應用的都會遇到SqlHelper。每個人實現的也不同,網上現成的例子也很多。但在實際操作中,大部分都不實用。什么樣的才是實用的?答:適合應用場景的!
下面來介紹下我寫的一個關於Oracle的SqlHelper。沒有進行大規模及性能測試。
首先來說下為什么寫這個SqlHelper。在以往的桌面程序開發中,我遇到最多的Sql操作,一般不會涉及多個表同時操作,即使有,在使用SqlHelper時用一個Transaction就可以了。
但現在場景換了。在Web里我們將業務操作與具體數據庫操作分離了成 Ba 與Da(人為強制性的)。在Ba里對業務操作進行必要的檢測,然后調用Da讀寫數據。Ba可以供其它Ba操作時復用。
在復用時,我們要確保多個以及多級Ba在同一個事務(如果需要)里,同時Ba只能訪問自己的Da。這樣的操作環境催生了現在我要展示的SqlHelper。然而,當我寫完了這個SqlHelper后,發現它可以很好的完成桌面以及Web應用中對Sql的操作。下面是桌面應用的測試用例
public class Ta : Blo { public int InsertAndNext() { var sql = "INSERT INTO TB_SYS_DD(DDID,DICNAME) values('{0}','{1}') returning DDID into :result";//注意結尾不需要使用 ; Trans.SqlHelper.CommandText = string.Format(sql, "2001", "測試項"); var para = new OracleParameter("result", OracleType.Number); para.Direction = ParameterDirection.Output; Trans.SqlHelper.AddParameter(para); Trans.SqlHelper.Execute(); return Convert.ToInt32(para.Value); } public void Add() { var sql = "INSERT INTO TB_SYS_DD values('{0}','{1}')"; Trans.SqlHelper.CommandText = string.Format(sql, "2001", "測試項"); Trans.SqlHelper.Execute(); } } public class Tb : Blo { public void Add() { var sql = "INSERT INTO TB_SYS_DDDETAIL values('{0}','{1}','{2}')"; Trans.SqlHelper.CommandText = string.Format(sql, "2000", "1", "測試項一"); Trans.SqlHelper.Execute(); } public string GetMaxDicName() { var sql = "select d.dicname from TB_SYS_DD d where d.ddid = (select max(ddid) from TB_SYS_DD)"; Trans.SqlHelper.CommandText = sql; var x = Trans.SqlHelper.ExecuteScalar(); return x.ToString(); } } public class Tc : Blo { public void Add() { var sql = "INSERT INTO TB_SYS_RESETPWD(userId,guid,Time) values('{0}','{0}',)"; Trans.SqlHelper.CommandText = string.Format(sql, "2000", "1"); Trans.SqlHelper.Execute(); } public void Update() { var sql = "update TB_SYS_DD set dicname = '{0}' where ddid = 2000"; Trans.SqlHelper.CommandText = string.Format(sql, "值"); Trans.SqlHelper.Execute(); } }
測試代碼
//直接使用sqlhelper的情況
private void button1_Click(object sender, EventArgs e) { using (var helper = new OracleHelper { ConnectionString = txtOracle.Text, CommandText = txtSql.Text }) { Trace.WriteLine(helper.DataSource); var dt = helper.GetDataTable(); if (dt != null && dt.Rows.Count > 0) { foreach (DataRow row in dt.Rows) { lbResult.Items.Add(row.Field<string>("TABLE_NAME") + "\t" + row.Field<string>("COMMENTS")); } } } } //使用業務邏輯創建一個事務。將所有的操作包含在同一事務里 private void button2_Click(object sender, EventArgs e) { var a = new Ta(); //a.Trans.SqlHelper.ConnectionString = txtOracle.Text; a.Trans.Begin(); //下面的兩過程效果相同。但建議使用第二種。更容易理解 var b = new Tb {Trans = a.Trans}; //var b = new Tb(); //a.Trans.AddBusiness(b); var c = new Tc(); a.Trans.AddBusiness(c); try { a.Add(); b.Add(); c.Add(); a.Trans.Commit(); } catch (Exception ex) { a.Trans.Rollback(); throw new Exception(); } } //插入之后返回值,如自增Id private void button3_Click(object sender, EventArgs e) { var a = new Ta(); a.Trans.SqlHelper.ConnectionString = txtOracle.Text; a.Trans.Begin(); try { var i = a.InsertAndNext(); MessageBox.Show(i.ToString()); a.Trans.Commit(); } catch (Exception ex) { a.Trans.Rollback(); throw; } } /* 在一個事務里,對N個表進行操作(1)。 這時有其它的連接對相同的表操作。操作是按正常情況執行(2)。 若(2)使用(1)表中的結果,那么如果(1)的事務未提交,則(2)無法訪問(1)中最新的值 */ private void button4_Click(object sender, EventArgs e) { var a = new Ta(); a.Trans.SqlHelper.ConnectionString = txtOracle.Text; a.Trans.Begin(); var b = new Tb(); a.Trans.AddBusiness(b); var c = new Tc(); a.Trans.AddBusiness(c); try { a.Add(); Thread.Sleep(1000 * 60 * 5);//等5分鍾 b.Add(); c.Add(); a.Trans.Commit(); } catch (Exception ex) { a.Trans.Rollback(); throw; } } private void button5_Click(object sender, EventArgs e) { var b = new Tb(); b.Trans.SqlHelper.ConnectionString = txtOracle.Text; try { var x = b.GetMaxDicName(); MessageBox.Show(x); } catch (Exception ex) { throw; } } private void button6_Click(object sender, EventArgs e) { var c = new Tc(); c.Trans.SqlHelper.ConnectionString = txtOracle.Text; try { c.Update(); } catch (Exception ex) { throw; } } //外層事務,內層獨立事務 private void button7_Click(object sender, EventArgs e) { var a = new Ta(); a.Trans.SqlHelper.ConnectionString = txtOracle.Text; a.Trans.Begin(); var b = new Tb(); b.Trans.SqlHelper.ConnectionString = txtOracle.Text; var c = new Tc(); c.Trans.SqlHelper.ConnectionString = txtOracle.Text; try { a.Add(); try { b.Trans.Begin(); b.Add(); b.Trans.Commit(); } catch (Exception) { b.Trans.Rollback(); throw; } try { c.Trans.Begin(); c.Add(); c.Trans.Commit(); } catch (Exception) { c.Trans.Rollback(); throw; } a.Trans.Commit(); } catch (Exception ex) { a.Trans.Rollback(); throw; } } }
看完以上代碼。你現在最關心的是如何實現事務共享的,以及如何區分事務的所有者及是否可以進行提交。
秘密其實很簡單,能過三個地方來控件。
1、是否共享
public class Blo 為業務對象基類,我給它一個事務控制的屬性對象 public Trans Trans { get; set; }
2、Trans 對象為事務控制器。給它設定一個屬性 public bool IsInherited { get; set; } 用於標識它的事務是否從其它事務得來的。
3、Blo 的 Trans 的屬性讀寫器
public Trans Trans { get { if (_trans != null) return _trans; _trans = new Trans(this) {IsInherited = false}; return _trans; } set { if (_trans == null) { _trans = new Trans(this); } _trans.SqlHelper = value.SqlHelper; _trans.IsInherited = true; } }
通過這三個地方我們就完全可以控件事務的統一訪問了。
說了這么多還是把dll文件上傳上來。如果哪個兄弟有心可以對此dll進行性能測試。當然需要.net 4