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