Winform开发之离线式WCF开发框架的实现介绍

在上篇随笔《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++, "住院信息");
            }
        }

 

 

 

posted on 2012-12-01 12:43  伍华聪  阅读(7170)  评论(5编辑  收藏  举报

导航