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);
            }
    ......................

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

  • 相关阅读:
    【转】win8.1下安装ubuntu
    Codeforces 1025G Company Acquisitions (概率期望)
    Codeforces 997D Cycles in Product (点分治、DP计数)
    Codeforces 997E Good Subsegments (线段树)
    Codeforces 1188E Problem from Red Panda (计数)
    Codeforces 1284E New Year and Castle Building (计算几何)
    Codeforces 1322D Reality Show (DP)
    AtCoder AGC043C Giant Graph (图论、SG函数、FWT)
    Codeforces 1305F Kuroni and the Punishment (随机化)
    AtCoder AGC022E Median Replace (字符串、自动机、贪心、计数)
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/3409596.html
Copyright © 2011-2022 走看看