在上篇隨筆《Winform開發框架之框架演化》中介紹了幾種Winform開發框架,其中有對於離線式WCF開發框架的介紹,離線式的WCF開發框架 ,就是結合了傳統Winform開發框架的數據訪問方式,又利用了WCF分布式數據獲取的特點,使得數據可以離線使用,在一種業務要求集中化,又要求不影響正常業務操作的應用系統場景下比較適合。本文主要介紹如何利用我的Winform開發框架的整體思路,實現WCF開發框架的離線式的數據上傳、更新的同步操作。
其實目前企業集中化管理,這種模式要求很多,如一些加盟店的情況,需要獨立運行,有可以對一些總店關鍵數據進行提交或者下載,如客戶信息等。這種情況下,就要求我們開發者提供適合應用場景的開發框架進行支持。離線式的WCF開發框架,一個特點就是基本上顯示,以及保存等操作數據庫的數據,都是本地的數據庫,不是遠端的服務器數據庫,這樣,就需要記錄所有發生變更的數據庫操作,包括寫入,刪除、修改等,以便在網絡暢通的情況下,可以上傳數據到服務器上面。
下面我們來分析下這種離線式的WCF開發框架,需要做哪些准備工作,來實現框架的支撐。
1、數據庫表記錄ID定義唯一性。
這個是常見分布式系統的要求了,在一些普通的Winform程序的數據庫中也比較常見,之所以把它作為第一條,雖然簡單,但是很必要,因為需要避免分布式的客戶端和服務端的數據沖突問題,特別在多個客戶端的情況下,對數據的唯一性要有好的控制性。
所以這也要求基礎的框架基類,能夠提供對整形、字符型的主鍵ID的操作兼容性,這在我的Winform開發框架中,支持是比較好的。
2、多數據庫支持
在分布式的環境下,和服務端的環境不同,部署程序要求越簡單越好,太復雜的話,增加客戶端的使用的難度,會極大提高維護的成本,因此,一般客戶端會選用適應性比較好,又免安裝的數據庫,如Sqlite就是一個很好的單機版數據庫,還有Access也是很不錯的,當然還有其他的一些數據庫,不過我覺得Sqlite和Access是比較好的備選方案。服務器端的數據庫,則看業務支持和響應程度來決定,可以從一些對性能支持比較好的數據庫中選型,如大型一點的,可選擇Oracle來做,其他的可以選擇SqlServer、MySql等數據庫。雖然這些數據庫部署比較麻煩一點,不過反正只有一台服務器需要這種安裝部署,所以工作難度及工作量不會很大。
對多數據庫的支持,也要求我們的開發框架能夠很好兼容,最好在數據庫操作層可以通過配置方式進行切換,即使數據庫變化為其他類型,也不需要改變整體的框架布局,甚至不用變化代碼即可實現自由切換,如數據庫框架可以設置如下。
對於上面幾種數據庫的支持,一般來說,需要增加不同數據庫類型的BaseDAL,由於每個不同數據庫都需要擁有一個BaseDAL,那么很多相同的操作代碼就會發生冗余,因為大多數數據庫的基礎操作是一樣的,只有一部分比較特別,需要進行個性化處理,因此對數據訪問層進行優化設計,得到下面的設計圖,如下所示。
經過框架抽象,這個BaseDAL類代碼很少,基本上通用的數據庫操作,已經放到了AbStractBaseDAL超級基類進行封裝,即使對於一些不同數據庫操作不同,我們也盡可能抽象放到上面基類了,BaseDAL只需要實現一些特殊的操作即可。
3、分布式客戶端數據上傳設計
由於分布式,離線式的框架設計,要求我們客戶端自行記錄數據的變化情況,包括新增數據、修改數據和刪除數據,這樣不用每次同步的時候,把所有的數據庫記錄都遍歷一次,然后和服務器記錄進行比較。這種記錄方式,可以極大提高客戶端數據上傳的性能和快捷性。因為我們對於很多表及記錄的數據庫,可能每次更新的只是一小部分,這樣設計,有利於我們更好地額處理客戶端數據上傳。
例如,下面的表,就是對於一個客戶端上傳記錄表的設計,其中Dept_ID是用來記錄不同部門的表示,基本上每個客戶端,都有自己的一個部門編號,防止數據發生沖突,也方便服務器端的數據進行歸類查詢。
下面是一些實際業務產生的數據記錄,我們記錄部門ID、表名(發生變化)、對應記錄的ID(GUID)、修改用戶、修改時間等信息。
另外,我們還可以結合系統來記錄用戶登錄信息、用戶對記錄修改的日志,以便我們對一些關鍵操作進行審計需要。數據庫設計如下所示。
4、數據修改記錄自動記錄
對於上面的to_upload表,我們是把客戶端修改的數據記錄信息,記錄到表里面去,但是這些肯定是后台自動記錄的,而且這個操作是放到基類比較合適,否則每次調用,不太方便,也比較冗余。
放到基類的操作,我們需要設計一下,否則所有的表都會記錄,不管需不需要,這樣不可以的。
首先我們在基類BaseDAL(對Sqlite的數據庫基類),增加一個變量來記錄是否數據庫訪問基類,需要記錄數據庫變化信息。
protected bool IsLogToUpoad = false; //表示是否記錄變化
對於具體業務對象的數據訪問,我的Winform開發框架都有提供一個對應的類來進行操作。
/// <summary> /// 葯品信息 /// </summary> public class DrugDetail : BaseDAL<DrugDetailInfo>, IDrugDetail { #region 對象實例及構造函數 public static DrugDetail Instance { get { return new DrugDetail(); } } public DrugDetail() : base("M_DrugDetail","ID") { this.IsLogToUpoad = true; this.sortField = "EditTime"; this.isDescending = true; } #endregion
..........................................
為了要實現自動記錄數據庫變化信息,我們需要在BaseDAL里面對插入、修改、刪除的操作進行特別的處理,重載基類的操作,增加相應的處理即可,如下代碼所示。
private void AddToUpload(string id, string targetTable, System.Data.Common.DbTransaction trans, int uploadType) { AppConfig config = new AppConfig(); ToUploadInfo info = new ToUploadInfo(); info.EditTime = DateTime.Now; info.RecordId = id; info.TableName = targetTable; info.UploadType = uploadType; info.Dept_ID = config.AppConfigGet("Dept_ID"); ; info.User_ID = config.AppConfigGet("User_ID"); Hashtable uploadHash = GetHashByObject(info); base.Insert(uploadHash, "SS_ToUpload", trans); } public override bool PrivateUpdate(object id, Hashtable recordField, string targetTable, DbTransaction trans) { bool result = base.PrivateUpdate(id, recordField, targetTable, trans); if (result && IsLogToUpoad) { AddToUpload(recordField["ID"].ToString(), targetTable, trans, 1); } return result; } public override bool Insert(System.Collections.Hashtable recordField, string targetTable, System.Data.Common.DbTransaction trans) { bool result = base.Insert(recordField, targetTable, trans); if (result && IsLogToUpoad) { AddToUpload(recordField["ID"].ToString(), targetTable, trans, 0); } return result; }
由於是上面的PrivateUpdate和Inser方法,是所有派生的更新、插入接口的最原始的函數,所有其他相關函數都會調用這兩個的基礎函數, 這樣就基本實現了數據庫記錄的變化記錄了。
5、分布式客戶端數據和服務器端的同步
為了和服務器實現同步,需要實現變化記錄的上傳和服務器修改數據的下載兩個方向的工作。
變化記錄的上傳從操作,就是遍歷to_upload里面的記錄,把它更新到服務器上即可。
/// <summary> /// 把本地變化的數據記錄,同步到服務器上 /// </summary> /// <returns></returns> public bool SyncAll() { List<ToUploadInfo> toList = BLLFactory<ToUpload>.Instance.GetAll(); int i = 1; int total = toList.Count; bool success = false; foreach (ToUploadInfo toInfo in toList) { switch(toInfo.TableName.ToLower()) {
case "m_drugusedetail": success = DealDrugUseDetail(toInfo); if (!success) return false; break;
....................................... //其他操作,利用服務器代理對象,實現各個表的數據上傳 } #region 顯示進度等處理 string tips = string.Format("正在同步表 {0}...", toInfo.TableName); int step = 0; if (total > 0) { step = Convert.ToInt32((100.0 / (1.0 * total)) * i); } if (OnDataDealed != null) { OnDataDealed(step, tips); } i++; if (toInfo == null || string.IsNullOrEmpty(toInfo.TableName)) { continue; } #endregion } #region 同步系統關鍵數據 if (success) { //部門 SynAllDept(); //已上傳數據表同步 DealUploaded(); SyncBasicData(); } #endregion return true; }
為了實現數據上傳操作,我們把邏輯封裝在一個函數里面,這樣方便管理,也方便閱讀。
private bool DealDrugUseDetail(ToUploadInfo toInfo) { bool success = false; DrugUseDetailInfo objInfo = BLLFactory<DrugUseDetail>.Instance.FindByID(toInfo.RecordId); if (objInfo != null && objInfo.Dept_ID == Portal.gc.LoginInfo.Dept_ID) { new DrugUseDetailServiceClient().Using(client => { success = client.InsertUpdate(objInfo, objInfo.ID); }); if (success) { RemoveToUploadInfo(toInfo); } } return success; }
數據同步的下載操作,其實也不難,就是把數據對應的記錄下載下來進行判斷。
private void DealInHospital() { List<InHospitalInfo> list = new List<InHospitalInfo>(); new InHospitalServiceClient().Using(client => { list = client.Find(conditionPilotDept); }); int i = 1; int total = list.Count; foreach (InHospitalInfo info in list) { BLLFactory<InHospital>.Instance.InsertUpdate(info, info.ID); ShowProgress(total, i++, "住院信息"); } }