Winform开发框架之通用数据导入导出操作的事务性操作完善
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Winform开发框架之通⽤数据导⼊导出操作的事务性操作完善1、通⽤数据导⼊导出操作模块回顾
在我的Winfrom开发框架⾥⾯,有⼀个通⽤的导⼊模块,它在默默处理这把规范的Excel数据导⼊到不同的对象表⾥⾯,⼀直⽤它来快速完成数据导⼊的⼯作。
很早在随笔《》⾥⾯就很全⾯的介绍过它的相关功能了,在代码⽣成⼯具Database2Sharp⾥⾯,⽣成的Winfrom界⾯代码也已经把它的调⽤代码放进去了,因此使⽤起来真是很好,很开⼼。
在不断的项⽬实践中,发现使⽤基于Sqlite的客户端作为单机版的操作也越来越多,因此⼤批量的数据导⼊,也是经常碰到的事情,我们知道,SqlServer批量插⼊数据会很快,即使你没有使⽤事务,⼀条条的插⼊,⼤批量也会⽐较快,这个可能得益于SqlServer本⾝的事务优化效果。
但是作为单机版的数据库,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)
{
mit();
}
我们看到,在通⽤的导⼊模块⾥⾯,我们只看到传递事务对象给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, bine(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 = ;
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);
}
......................
基于事务性的调整,优化了整个基类接⼝和实现类的类库,以⽅便在框架中更好整合事务性操作的⽀持。