zoukankan      html  css  js  c++  java
  • Winform开发框架之通用数据导入导出操作的事务性操作完善

    1、通用数据导入导出操作模块回顾

    在我的Winfrom开发框架里面,有一个通用的导入模块,它在默默处理这把规范的Excel数据导入到不同的对象表里面,一直用它来快速完成数据导入的工作。很早在随笔《Winform开发框架之通用数据导入导出操作》里面就很全面的介绍过它的相关功能了,在代码生成工具Database2Sharp里面,生成的Winfrom界面代码也已经把它的调用代码放进去了,因此使用起来真是很好,很开心。

    在不断的项目实践中,发现使用基于Sqlite的客户端作为单机版的操作也越来越多,因此大批量的数据导入,也是经常碰到的事情,我们知道,SqlServer批量插入数据会很快,即使你没有使用事务,一条条的插入,大批量也会比较快,这个可能得益于SqlServer本身的事务优化效果。但是作为单机版的数据库,Sqlite每次操作都是单独一个事务的,插入一条数据效率可能不明显,如果操作一千条,一万条,数据的缓慢就很明显,甚至不可忍耐了。我曾经在《使用事务操作SQLite数据批量插入,提高数据批量写入速度,源码讲解》里面提到了批量插入通用字典模块的字典数据,使用事务前后批量插入数据,那个速度可是差别很大。

    基于以上的因素考虑,决定对通用的数据导入模块进行事务性的优化,以便适应我频繁使用Sqlite数据库大批量导入数据的情况,提高客户的良好体验。本篇主要基于事务性操作的完善,实现基于Sqlite数据的批量快速导入操作。

    2、事务性代理事件的定义

    由于是通用的模块,所以我们不知道具体的数据库事务对象,但是我们能够通过定义一些事件,给调用者进行事务对象的传递,这样才能在基类中使用事务对象,首先我们定义两个委托事件,一个是SaveDataHandler,用来进行单条数据的处理委托,一个是CreateTransactionHandler,让调用者创建并传递事务对象的委托,具体代码如下所示。

        public partial class FrmImportExcelData : BaseForm
        {
            ...............................
            private DbTransaction transaction = null;
    
            /// <summary>
            /// 使用事务对数据进行保存的委托,加快速度
            /// </summary>
            /// <param name="dr">数据行</param>
            /// <param name="trans">事务对象</param>
            /// <returns></returns>
            public delegate bool SaveDataHandler(DataRow dr, DbTransaction trans);
    
            /// <summary>
            /// 创建事务对象的委托,在导入操作初始化的时候赋值
            /// </summary>
            /// <returns></returns>
            public delegate DbTransaction CreateTransactionHandler();

    定义好委托后,我们需要创建对应委托的事件对象,作为通用模块的事件,如下所示。

            /// <summary>
            /// 保存数据事件
            /// </summary>
            public event SaveDataHandler OnDataSave;
    
            /// <summary>
            /// 刷新数据事件
            /// </summary>
            public event EventHandler OnRefreshData;
    
            /// <summary>
            /// 让调用者创建事务并传递给通用模块
            /// </summary>
            public event CreateTransactionHandler OnCreateTransaction;

    在实现数据导入前,我们需要使用事件来获取对应的事务对象,以便开始事务,具体代码如下所示。

                if (MessageDxUtil.ShowYesNoAndWarning("该操作将把数据导入到系统数据库中,您确定是否继续?") == DialogResult.Yes)
                {
                    if (myDs != null && myDs.Tables[0].Rows.Count > 0)
                    {
                        DataTable dt = myDs.Tables[0];
                        this.progressBar1.Visible = true;
                        if (!worker.IsBusy)
                        {
                            if (OnCreateTransaction != null)
                            {
                                transaction = OnCreateTransaction();
                            }
                            worker.RunWorkerAsync();
                        }
                    }     
                }

    3、事务处理逻辑及调用者使用逻辑

    这样,我们在通用模块里面,获取到Excel数据后,需要遍历每行数据,然后通过事务对象实现数据提交,部分代码如下所示。

                        #region 批量保存数据,然后事务提交
                        foreach (DataRow dr in dt.Rows)
                        {
                            if (OnDataSave != null)
                            {
                                try
                                {
                                    bool success = OnDataSave(dr, transaction);
                                    if (success)
                                    {
                                        itemCount++;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    LogTextHelper.Error(ex);
                                    MessageDxUtil.ShowError(ex.Message);
                                }
                            }
    
                            int currentStep = Convert.ToInt32(step * i);
                            worker.ReportProgress(currentStep);
                            i++;
                        } 
                        #endregion
    
                        if (transaction != null)
                        {
                            transaction.Commit();
                        }

    我们看到,在通用的导入模块里面,我们只看到传递事务对象给OnDataSave(dr, transaction)事件,并最终提交整个事务处理而已,具体的

    从以上的代码看到,我们把创建事务对象的方法留给调用者实现OnCreateTransaction事件接口,保存每行数据,也留给调用者实现数据的保存OnDataSave事件。

    具体的模块调用代码如下所示。

            private string moduleName = "药品目录";
            private void btnImport_Click(object sender, EventArgs e)
            {
                string templateFile = string.Format("{0}-模板.xls", moduleName);
                FrmImportExcelData dlg = new FrmImportExcelData();
                dlg.SetTemplate(templateFile, System.IO.Path.Combine(Application.StartupPath, templateFile));
                dlg.OnDataSave += new FrmImportExcelData.SaveDataHandler(ExcelData_OnDataSave);
                dlg.OnCreateTransaction += new FrmImportExcelData.CreateTransactionHandler(dlg_OnCreateTransaction);
                dlg.OnRefreshData += new EventHandler(ExcelData_OnRefreshData);
                dlg.ShowDialog();
            }
    
            DbTransaction dlg_OnCreateTransaction()
            {
                return BLLFactory<DrugDetail>.Instance.CreateTransaction();
            }
    
            void ExcelData_OnRefreshData(object sender, EventArgs e)
            {
                BindData();
            }
    
            bool ExcelData_OnDataSave(DataRow dr, DbTransaction trans)
            {
                string drugNo = dr["药品编码"].ToString();
                string drugName = dr["药品名称"].ToString();
                if (string.IsNullOrEmpty(drugNo) && string.IsNullOrEmpty(drugName))
                    return false;
    
                bool success = false;
                DrugDetailInfo info = new DrugDetailInfo();
                info.DrugNo = drugNo;
                info.DrugName = drugName;
                info.Manufacture = dr["制造商"].ToString();
                info.Formulations = dr["剂型"].ToString();
                info.Specification = dr["规格"].ToString();
                info.Unit = dr["药品单位"].ToString();
                info.Note = dr["备注信息"].ToString();
                info.StockQuantity = ConvertHelper.ToInt32(dr["库存量"].ToString(), 0);
    
                info.EditTime = DateTime.Now;
                info.Editor = Portal.gc.LoginInfo.Name;
                info.Dept_ID = Portal.gc.LoginInfo.Dept_ID;
                success = BLLFactory<DrugDetail>.Instance.Insert(info, trans);
                return success;
            }

    写到这里,可能很多时候大家觉得随笔应该画上句号了吧,其实不然,还有很重要一个地方,需要提及一下,就是我们使用了事务保存数据,那么如果需要在单条记录保存的时候,需要判断检索数据,才决定插入还是更新操作呢?

    如果你觉得随便写一个select语句调用不就可以了吗?那样可能就会有问题了,事务性操作会锁定当前的表,不会让你继续写入了,很快就会得到操作超时的错误异常了。

    那么我们应该如何解决这种需求呢?就是你要使用事务的数据库连接对象,来实现数据的检索就可以了,如下面的代码就是OK的了。

            bool dlg_OnDataSave(DataRow dr, DbTransaction trans)
            {
                string PlaneModel = dr["装备型号"].ToString();
                if (string.IsNullOrEmpty(PlaneModel)) return false;
    
                bool success = false;
                PlaneModelInfo info = BLLFactory<PlaneModel>.Instance.FindSingle(string.Format("PlaneModel='{0}'", PlaneModel), trans);
                if (info != null)
                {
                    info.PlaneModel = PlaneModel;
                    info.PlaneNote = dr["保障特点"].ToString();
                    info.Demand = dr["保障要求"].ToString();
                    info.Note = dr["备注"].ToString();
    
                    info.Dept_ID = Portal.gc.LoginInfo.Dept_ID;
                    success = BLLFactory<PlaneModel>.Instance.Update(info, info.ID, trans);
                }
                else
                {
                    info = new PlaneModelInfo();
                    info.PlaneModel = PlaneModel;
                    info.PlaneNote = dr["保障特点"].ToString();
                    info.Demand = dr["保障要求"].ToString();
                    info.Note = dr["备注"].ToString();
    
                    info.Dept_ID = Portal.gc.LoginInfo.Dept_ID;
                    success = BLLFactory<PlaneModel>.Instance.Insert(info, trans);
                }
                return success;
            }

     

    4、Winform开发框架的事务接口支持

    基于此,我们很多查找的接口可能都会在事务中调用,需要重新构造我的框架基类接口了,把事务作为默认的对象参数,默认为NULL,调整我的基类,为所有的事务内操作提供支持,如数据访问接口层部分接口定义如下所示。

        /// <summary>
        /// 数据访问层的接口
        /// </summary>
        public interface IBaseDAL<T> where T : BaseEntity
        {
            #region 通用操作
    
            /// <summary>
            /// 获取表的所有记录数量
            /// </summary>
            /// <param name="trans">事务对象</param>
            /// <returns></returns>
            int GetRecordCount(DbTransaction trans = null);
    
            /// <summary>
            /// 获取表的指定条件记录数量
            /// </summary>
            /// <param name="condition">条件语句</param>
            /// <param name="trans">事务对象</param>
            /// <returns></returns>
            int GetRecordCount(string condition, DbTransaction trans = null);
    
            /// <summary>
            /// 根据condition条件,判断是否存在记录
            /// </summary>
            /// <param name="condition">查询的条件</param>
            /// <param name="trans">事务对象</param>
            /// <returns>如果存在返回True,否则False</returns>
            bool IsExistRecord(string condition, DbTransaction trans = null);
    
            /// <summary>
            /// 查询数据库,检查是否存在指定键值的对象
            /// </summary>
            /// <param name="recordTable">Hashtable:键[key]为字段名;值[value]为字段对应的值</param>
            /// <param name="trans">事务对象</param>
            /// <returns>存在则返回<c>true</c>,否则为<c>false</c></returns>
            bool IsExistKey(Hashtable recordTable, DbTransaction trans = null);
    
    ...................................

    BaseBLL业务基类的部分接口实现如下所示

        /// <summary>
        /// 业务基类对象
        /// </summary>
        /// <typeparam name="T">业务对象类型</typeparam>
        public class BaseBLL<T> where T : BaseEntity, new()
        {
    ............................
    
            #region 对象添加、修改、查询接口
    
            /// <summary>
            /// 插入指定对象到数据库中
            /// </summary>
            /// <param name="obj">指定的对象</param>
            /// <param name="trans">事务对象</param>
            /// <returns>执行操作是否成功。</returns>
            public virtual bool Insert(T obj, DbTransaction trans = null)
            {
                CheckDAL();
                return baseDal.Insert(obj, trans);
            }
    
            /// <summary>
            /// 插入指定对象到数据库中
            /// </summary>
            /// <param name="obj">指定的对象</param>
            /// <param name="trans">事务对象</param>
            /// <returns>执行成功返回新增记录的自增长ID。</returns>
            public virtual int Insert2(T obj, DbTransaction trans = null)
            {
                return baseDal.Insert2(obj, trans);
            }
    
            /// <summary>
            /// 更新对象属性到数据库中
            /// </summary>
            /// <param name="obj">指定的对象</param>
            /// <param name="primaryKeyValue">主键的值</param>
            /// <param name="trans">事务对象</param>
            /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
            public virtual bool Update(T obj, object primaryKeyValue, DbTransaction trans = null)
            {
                CheckDAL();
                return baseDal.Update(obj, primaryKeyValue, trans);
            }
    ......................

    基于事务性的调整,优化了整个基类接口和实现类的类库,以方便在框架中更好整合事务性操作的支持。

  • 相关阅读:
    Apache CXF实战之四 构建RESTful Web Service
    使用CXF开发RESTFul服务
    Java多线程中的异常处理
    Checked异常和Runtime异常体系
    hql 语法与详细解释<转>
    hibernate实现有两种配置,xml配置与注释配置。<转>
    Hibernate配置详细解释
    重构(Refactoring)技巧读书笔记(General Refactoring Tips)
    Hibernate(1)——数据访问层的架构模式<转>
    关于layer.photos即照片显示的问题。
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/3409596.html
Copyright © 2011-2022 走看看