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

基於事務性的調整,優化了整個基類接口和實現類的類庫,以方便在框架中更好整合事務性操作的支持。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM