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); } ......................
基於事務性的調整,優化了整個基類接口和實現類的類庫,以方便在框架中更好整合事務性操作的支 持。
伍華聰 http://www.iqidi.com