在上篇随笔《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++, "住院信息"); } }
本文转自博客园伍华聪的博客,原文链接:Winform开发之离线式WCF开发框架的实现介绍,如需转载请自行联系原博主。