最近一邊參與公司的項目開發,一邊還肩負著基礎庫的建立和維護。真真切切的體會到寫架構的不容易,寫出好的,方便使用的架構更不容易,需要考慮的東西太多,需要掌握的東西太多。不過不要緊我們正在前進的道路上。同志們一起加油!
最近在使用預存程序的時候總覺得有點麻煩,儘管在前期對ORM和統一資料來源介面封裝已經下了很多功夫,對IDataParameter之類的介面已經進行了很好的封裝,但是還是覺得麻煩。[王清培著作權,轉載請給出署名]
經過與DBA的溝通,他認為對預存程序的封裝是有必要的,以他十幾年的經驗看,預存程序後期的移植是必不可少的。現在的項目是用SQLSERVER2008開發的,後期可能會移植到ORACLE上去,那麼對預存程序的編寫DBA考慮很周全。但是對於程式員來說,經驗稍微豐富點的可能會通過某種工廠將具體對象脫耦,或者使用依賴倒置的原則來解決更換資料來源問題。但是考慮到統一的使用方法,這裡還是真的有必要進行封裝的。那麼如何封裝?
代碼產生器的重要性這裡為什麼要牽扯到代碼產生器呢?從我剛開始準備編寫基礎庫的時候我就意識到代碼產生器的重要性,當時的想法就是能為了完全的控制碼產生器。如果使用第三方的代碼產生器可能在初期是可以滿足要求,但是如果想把它做成成熟的開發平台是行不通的。藉助代碼產生器的功能,基礎庫的使用將變的更加流暢(後面將看到效果)。很明顯的就是ORM和一些IDE中內建的代碼產生,結合的很完美,讓人癡迷。[王清培著作權,轉載請給出署名] 我們假設沒有代碼產生器,那些諸如通用的代碼將需要我們程式員手動的敲,那個工作即枯燥也容易出錯。所以需要代碼產生器去幫我們產生我們想要的,符合某種規則的重複性的代碼。比較典型的就是我們三層架構中必不可少的Model集合(有個概念要糾正一下,常常有程式員將Model對象集讀成Model層,它並非層中的“層”,而是層中傳遞資料的結構)。有了自己公司的代碼產生器之後,就可以按照公司的開發架構去不斷的增強功能,使其逐漸層成開發平台,成熟了之後可能就是一套符合行業要求的快速開發架構。這也是個慢慢積累的過程,急不來。
預存程序的流量分析我假設我們已經對IDataParameter對象進行了封裝,我想對它簡單的封裝基本也都能滿足日常要求了。一般都是根據當前項目連結資料庫的類型字串進行判斷,然後產生相對應如:SqlParameter、OracleParameter、OleDbParameter等等,可能還包括一些開源的資料庫擴充架構中的對象,這裡的建立工廠可能是抽象工廠,當然方法很多種,實現效果就行。[王清培著作權,轉載請給出署名]對其簡單的封裝我們在使用的時候需要使用Factory 方法建立IDataParameter數組,如:
Dictionary<string, object> parameter = new Dictionary<string, object>();parameter.Add("PurchaseID", Purchase.TempSerialNo);//單據流水號 parameter.Add("WarehouseId", Purchase.InWarehouseID);//倉庫IDparameter.Add("UserID", Purchase.UserID);//操作IDparameter.Add("UserName", Purchase.UserName);//操作人名稱parameter.Add("PurchaseDate", DBNull.Value);//採購日期parameter.Add("BuyUserID", DBNull.Value);//採購人編號parameter.Add("BuyUserName", DBNull.Value);//採購人名稱parameter.Add("BuyDate", DBNull.Value);//採購日期parameter.Add("Memo", Purchase.Memo);//備忘說明IDataParameter[] parameterDic = IDataParameterFactory.CreateDbDataParameter(parameter); List<IDataParameter> listparameter = IDataParameterHelper.IDataParameterArrayToList(parameterDic);listparameter.Add(WL.DAL.DAL_TB_WLPurchase.GetErrIDParametere());using (Fast.Orm.IDataSourceOperation operation = Fast.Orm.IDataSourceOperationFactory.Create()){operation.ExecuteNonQuery(CommandType.StoredProcedure, "prc_WLPurchaseTmpAdd", listparameter.ToArray());if (listparameter[listparameter.Count - 1].Value.ToString() == "0")return true;return false;}
一般性的封裝基本都這樣或者在IDataParameterFactory.CreateDbDataParameter(Entity)中加入根據實體的屬性動態建立IDataParameter[]對象,如果你的建立始終是使用反射的話那麼將是不可取的。有興趣的朋友可以參見本人的另一篇文章“利用抽象、多態實現無反射的綠色環保ORM架構”對實體的使用如果不能擺脫反射,那麼在以後的基礎庫擴充中將面臨著很多效能問題,這裡需三思。
由於很少預存程序的參數名稱都是對應的實體的屬性名稱,這種對應關係很難做到,或者說是做到的話需要DBA花點時間呢,在命名上也是個約束。如果預存程序有N個參數的話我們需要對照資料庫設計文檔來編寫IDictionary項,在一般的項目中都將複雜的商務邏輯封裝在預存程序中實現,所以預存程序的數量也是不少的。這樣一來也算是一個比較浪費時間的工作。那麼如果減少編碼量,讓預存程序的調用變的簡單,而且對使用者來說是透明的?
抽象預存程序的參數使其變成參數實體抽象由於在設計綠色ORM的過程中總結了很多好的想法,也確實能感覺到對簡單實體的抽象能使後期的擴充變的更加自如。比如,不需要那麼費力的使用反射擷取屬性中繼資料,直接使用字典集合就能得到屬性的名稱和值。那麼我也使用類似的設計思路來設計了參數實體對象。首先需要抽象的基類,用來儲存對預存程序的一個簡單的對應關係,請看代碼:
/// <summary> /// 預存程序實體(參數資訊類)基類 /// </summary> public abstract class BaseStoredprocedureObject : DictionaryBase { /// <summary> /// 受保護的欄位-預存程序名稱 /// </summary> protected string procedurename = string.Empty; /// <summary> /// 受保護的欄位-命令參數集合 /// </summary> protected List<IDataParameter> parameterlist = new List<IDataParameter>(); /// <summary> /// 擷取命令參數集合 /// </summary> public List<IDataParameter> ParameterList { get { return parameterlist; } } /// <summary> /// 添加IDataParameter對象到基類parameterlist對象 /// </summary> public abstract void AddParameterToBaseParameterObject(); /// <summary> /// 擷取預存程序名稱 /// </summary> public string ProcedureName { get { return procedurename; } } /// <summary> /// 擷取對應參數名稱的值 /// </summary> /// <param name="keyname">參數名稱</param> /// <returns>object:參數值</returns> public object this[string keyname] { get { return this.Dictionary[keyname]; } internal set { this.Dictionary[keyname] = value; } } /// <summary> /// 擷取所有參數資訊 /// </summary> public IDictionary GetProcedureEntity { get { return this.Dictionary; } } /// <summary> /// 預存程序返回的資料集 /// </summary> public DataTable Source { get; internal set; }}
具體的屬性都有注釋,我就不解釋了。可能這個對象在初期也是比較簡單的,隨著使用範圍的變大或者使用複雜,那麼這個類還需要其他的東西。這是抽象的對象,那麼在具體的子類當中是如何的呢?還是看代碼比較直觀;
using System;using System.Collections.Generic;using System.Text;using System.Data;using Fast.Orm;namespace Fast.WL.Parmeter{ [Serializable()] public class Init_prc_WLOrderTmpAdd : BaseStoredprocedureObject { public Init_prc_WLOrderTmpAdd() { this.procedurename = "prc_WLOrderTmpAdd"; this.Dictionary.Add("OrderID", null); this.Dictionary.Add("StationID", null); this.Dictionary.Add("UserID", null); this.Dictionary.Add("UserName", null); this.Dictionary.Add("OrderDate", null); this.Dictionary.Add("DeliveryAddress", null); this.Dictionary.Add("OrderType", null); this.Dictionary.Add("APNumber", null); this.Dictionary.Add("Memo", null); this.Dictionary.Add("ErrID", DBNull.Value); } public override void AddParameterToBaseParameterObject() { base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("OrderID", base.Dictionary["OrderID"],ParameterDirection.Input, DbType.String, 14)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("StationID", base.Dictionary["StationID"],ParameterDirection.Input, DbType.String, 36)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("UserID", base.Dictionary["UserID"],ParameterDirection.Input, DbType.String, 36)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("UserName", base.Dictionary["UserName"],ParameterDirection.Input, DbType.String, 10)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("OrderDate", base.Dictionary["OrderDate"],ParameterDirection.Input, DbType.DateTime, 8)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("DeliveryAddress", base.Dictionary["DeliveryAddress"],ParameterDirection.Input, DbType.String, 50)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("OrderType", base.Dictionary["OrderType"],ParameterDirection.Input, DbType.Int16, 2)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("APNumber", base.Dictionary["APNumber"],ParameterDirection.Input, DbType.String, 20)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("Memo", base.Dictionary["Memo"],ParameterDirection.Input, DbType.String, 220)); base.parameterlist.Add(IDataParameterFactory.CreateDbDataParameter("ErrID", base.Dictionary["ErrID"],ParameterDirection.Output, DbType.Int32, 4)); } } public class prc_WLOrderTmpAdd : Init_prc_WLOrderTmpAdd { public object OrderID { get { return this.Dictionary["OrderID"] as object; } set { this.Dictionary["OrderID"] = value; } } public object StationID { get { return this.Dictionary["StationID"] as object; } set { this.Dictionary["StationID"] = value; } } public object UserID { get { return this.Dictionary["UserID"] as object; } set { this.Dictionary["UserID"] = value; } } public object UserName { get { return this.Dictionary["UserName"] as object; } set { this.Dictionary["UserName"] = value; } } public object OrderDate { get { return this.Dictionary["OrderDate"] as object; } set { this.Dictionary["OrderDate"] = value; } } public object DeliveryAddress { get { return this.Dictionary["DeliveryAddress"] as object; } set { this.Dictionary["DeliveryAddress"] = value; } } public object OrderType { get { return this.Dictionary["OrderType"] as object; } set { this.Dictionary["OrderType"] = value; } } public object APNumber { get { return this.Dictionary["APNumber"] as object; } set { this.Dictionary["APNumber"] = value; } } public object Memo { get { return this.Dictionary["Memo"] as object; } set { this.Dictionary["Memo"] = value; } } public object ErrID { get { return this.Dictionary["ErrID"] as object; } set { this.Dictionary["ErrID"] = value; } } }}
在Init_prc_WLOrderTmpAdd建構函式中我們設定所有的參數名稱和預設的值,這裡可以會是DbNull.Value。[王清培著作權,轉載請給出署名]
在AddParameterToBaseParameterObject重寫方法中我們用來建立所有的IDataParameter對象的具體執行個體,由於不同的參數名稱,不同的資料類型,不同的輸入輸出。所以我們需要這麼一個建立IDataParameter對象的Factory 方法,這個方法應該在前期就已經存在了,這裡我假設它已經被建立了。那麼在使用的時候我們不需要關心太多的細節,只需要對將該對象當作執行預存程序的參數對象即可。
prc_WLOrderTmpAdd ordertmp = new prc_WLOrderTmpAdd(); ordertmp.OrderID = order.OrderID;//訂單流水號 ordertmp.StationID = order.StationID;//網站ID ordertmp.UserID = order.UserID; ordertmp.UserName = order.UserName; ordertmp.OrderDate = DBNull.Value; ordertmp.DeliveryAddress = order.DeliveryAddress; ordertmp.OrderType = order.OrderType; ordertmp.APNumber = string.IsNullOrEmpty(order.APNumber) ? DBNull.Value : (object)order.APNumber; ordertmp.Memo = DBNull.Value;//備忘 Fast.Orm.ProcedureHelper.ProcedureOperation<prc_WLOrderTmpAdd>(ordertmp); return int.Parse(ordertmp.ErrID.ToString()) == 0 ? true : false;
這樣保證我們寫的代碼都圍繞著資料實體來進行資料庫的操作。
只需要封裝一個簡單的執行預存程序的方法就行了。Fast.Orm.ProcedureHelper.ProcedureOperation<prc_WLOrderTmpAdd>(ordertmp);這裡我就不貼出代碼了。使用上是變的簡單了,那麼資料實體怎麼來呢?這裡就是我文章開頭小結講到的,代碼產生器對基礎架構的重要性。[王清培著作權,轉載請給出署名]
有了專業的代碼產生器之後,一切就變的簡單多了,我們按照自己的要求設計開發代碼產生器來配合基礎架構的使用,那麼我們的開發效率將大大提高了。
總結:這裡只是本人在封裝預存程序的使用時的一些小小的經驗,與大家分享一下,也算是一個拋磚引玉吧,可能大面積的使用會存在點未知的問題,不過架構就是這樣才變的穩定的,希望對大家有點用,謝謝。