Winform开发框架里面使用事务操作的原理及介绍

在很多情况下,事务是个很有用的东西,可以把一系列的操作组合成一个原子粒度的操作,一旦组合中某个地方出错,可以整个干净的进行滚回,不会留下脏数据;除此之外,事务还能提高批量操作的效率,如在本地SQLite数据库里面,批量插入1万条数据,那么使用事务和没有使用事务,速度上至少差别几十到上百倍的差异。既然事务有完整性和速度性的差异,因此,基于上述原因,我们在很多情况下最好使用事务进行操作。本文主要介绍在开发框架中如何整合事务的操作,并介绍在各个分层中的事务使用案例。
由于我介绍的相关框架,主要是采用了微软的Enterprise Library的数据库访问模块,因此它能够很好抽象各种数据库的事务,以适应各种不同数据库的事务处理。使用微软的Enterprise Library模块,可以很好支持SQLSever、Oracle、Mysql、Access、SQLite等数据库。

1、数据访问层中的事务操作

1.1 数据访问层的事务接口定义
由于使用事务操作,因此在底层的模块里面,也就是这里的数据访问层,一般需要一个事务对象的参数。如下代码是一个数据访问层的接口定义。

/// <summary>
/// 数据访问层的接口
/// </summary>
public interface IBaseDAL<T> where T : BaseEntity
{
    /// <summary>
    /// 插入指定对象到数据库中
    /// </summary>
    /// <param name="obj">指定的对象</param>
    /// <param name="trans">事务对象</param>
    /// <returns>执行成功返回True</returns>
    bool Insert(T obj, DbTransaction trans = null);
    
    /// <summary>
    /// 根据指定对象的ID,从数据库中删除指定对象
    /// </summary>
    /// <param name="key">指定对象的ID</param>
    /// <param name="trans">事务对象</param>
    /// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
    bool Delete(object key, DbTransaction trans = null);
    
    /// <summary>
    /// 更新对象属性到数据库中
    /// </summary>
    /// <param name="obj">指定的对象</param>
    /// <param name="primaryKeyValue">主键的值</param>
    /// <param name="trans">事务对象</param>
    /// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
    bool Update(T obj, object primaryKeyValue, DbTransaction trans = null);
    
    /// <summary>
    /// 查询数据库,检查是否存在指定ID的对象
    /// </summary>
    /// <param name="key">对象的ID值</param>
    /// <param name="trans">事务对象</param>
    /// <returns>存在则返回指定的对象,否则返回Null</returns>
    T FindByID(object key, DbTransaction trans = null);
            
    .....................//其他操作
    
}

从上面的代码上,我们可以看到,里面的增删改查等操作,最后都带一个trans的事务对象参数,这个参数默认为null,也就是可选参数的做法,这个方法就提供了两个重载的方法供我们使用。
看到这里,可能有些人提出疑问,为什么查找方法也传入事务对象,这个因为事务是一个排斥性操作,一旦启动了事务,可能这个表的其他操作会被锁定,但在这个事务内操作确实可以允许的,如果你使用SQLite这种单机版的数据库,在一个地方采用事务操作一个表,在事务内部接着不使用事务进行表的任何操作将不会被允许,数据库提示出错信息的。
基于这个原因,所有表操作的接口,都应该提供事务性的操作接口,也就是提供一个事务性的对象参数。在接口的实现里面,判断事务对象是否为空,然后进行相应的处理即可。
1.2 在DAL层自定义函数的事务操作
由于在IBaseDAL里面已经定义了很多事务性的接口,因此数据访问层的基类里面也已经实现了很多相关的基础操作。
因此数据访问层DAL层里面,如自己定义的实现函数,调用这些基础函数进行处理就可以了。自定义函数对事务的操作处理,代码如下所示。

/// <summary>
/// 调整客户的组别
/// </summary>
/// <param name="customerId">客户ID</param>
/// <param name="groupIdList">客户分组Id集合</param>
/// <returns></returns>
public bool ModifyCustomerGroup(string customerId, List<string> groupIdList)
{
    bool result = false;
    DbTransaction trans = base.CreateTransaction();
    if (trans != null)
    {
        string sql = string.Format("Delete from T_CRM_CustomerGroup_Customer where Customer_ID='{0}' ", customerId);
        base.SqlExecute(sql, trans);

        foreach (string groupId in groupIdList)
        {
            sql = string.Format("Insert into T_CRM_CustomerGroup_Customer(Customer_ID,CustomerGroup_ID) values('{0}', '{1}') ", customerId, groupId);
            base.SqlExecute(sql, trans);
        }

        try
        {
            trans.Commit();
            result = true;
        }
        catch
        {
            trans.Rollback();
            throw;
        }
    }
    return result;
}

2、业务逻辑层的事务操作

业务逻辑层BLL层是对数据访问层的更高一层的封装,它也相应提供相应的事务对象接口,以方便外部的调用。

它的业务类里面,自定义函数对事务的调用操作如下所示。

/// <summary>
/// 把报价单转换为销售订单
/// </summary>
/// <param name="quotationNo">报价单编号</param>
/// <returns></returns>
public bool ConvertToOrder(string orderNo, int userId)
{
    bool result = false;

    DbTransaction trans = baseDal.CreateTransaction();
    if (trans != null)
    {
        SellInfo sellInfo = ConvertSellInfo(orderNo, userId, trans);
        List<OrderDetailInfo> detailList = new List<OrderDetailInfo>();
        if (sellInfo != null)
        {
            detailList = ConvertOrderDetal(sellInfo, orderNo, trans);
        }

        bool success = BLLFactory<Sell>.Instance.Insert(sellInfo, trans);
        if (success)
        {
            foreach (OrderDetailInfo info in detailList)
            {
                BLLFactory<OrderDetail>.Instance.Insert(info, trans);
            }                    
        }

        try
        {
            trans.Commit();
            result = true;
        }
        catch
        {
            trans.Rollback();
            throw;//重新抛出异常
        }
    }
    return result;
}

另一例子如下所示。

/// <summary>
/// 删除报价单及明细信息
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool DeleteQuotationRelated(string id)
{
    bool result = false;
    DbTransaction trans = CreateTransaction();
    if (trans != null)
    {
        QuotationInfo info = baseDal.FindByID(id, trans);
        if (info != null)
        {
            List<QuotationDetailInfo> detailList = BLLFactory<QuotationDetail>.Instance.FindByOrderNo(info.HandNo, trans);
            foreach (QuotationDetailInfo detailInfo in detailList)
            {
                BLLFactory<QuotationDetail>.Instance.Delete(detailInfo.ID, trans);
            }

            //最后删除主表订单数据
            baseDal.Delete(id, trans);

            try
            {
                trans.Commit();
                result = true;
            }
            catch (Exception ex)
            {
                trans.Rollback();
                LogTextHelper.Error(ex);
                throw;
            }
        }
    }
    return result;
}
}

3、Winform界面层对事务的调用

由于Winform界面层,直接调用BLL层的相应接口,进行数据的操作的,因此我们也可以在界面层创建相应的事务对象,然后在界面层操作事务。

界面层调用事务处理,操作代码如下所示。

using (DbTransaction trans = BLLFactory<Function>.Instance.CreateTransaction())
{
    try
    {
        if (trans != null)
        {
            bool sucess = BLLFactory<Function>.Instance.Insert(mainInfo, trans);
            if (sucess)
            {
                FunctionInfo subInfo = null;
                int sortCodeIndex = 1;

                #region 子功能操作
                if (chkAdd.Checked)
                {
                    subInfo = CreateSubFunction(mainInfo);
                    subInfo.SortCode = (sortCodeIndex++).ToString("D2");
                    subInfo.ControlID = string.Format("{0}/Add", mainInfo.ControlID);
                    subInfo.Name = string.Format("添加{0}", mainInfo.Name);

                    BLLFactory<Function>.Instance.Insert(subInfo, trans);
                }
                if (chkDelete.Checked)
                {
                    subInfo = CreateSubFunction(mainInfo);
                    subInfo.SortCode = (sortCodeIndex++).ToString("D2");
                    subInfo.ControlID = string.Format("{0}/Delete", mainInfo.ControlID);
                    subInfo.Name = string.Format("删除{0}", mainInfo.Name);
                    BLLFactory<Function>.Instance.Insert(subInfo, trans);
                }
                ......................//其他事务操作
                #endregion

                trans.Commit();
                ProcessDataSaved(this.btnSave, new EventArgs());

                //this.DialogResult = System.Windows.Forms.DialogResult.OK;
                MessageDxUtil.ShowTips("保存成功");
            }
            else
            {
                MessageDxUtil.ShowTips("保存失败");
            }
        }
    }
    catch (Exception ex)
    {
        if (trans != null)
        {
            trans.Rollback();
        }

        LogTextHelper.Error(ex);
        MessageDxUtil.ShowError(ex.Message);
    }
}

以上就是我对不同分层中使用事务对象进行各种操作的处理,由于事务对象会对表进行某种锁定操作,因此数据库的整体性能可能有所降低,但是在保证某种组合操作的原子性,以及批量数据库操作这两种方式,该出手时还是要出手,这样能更好提高完整性和处理的高效性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • java事务的处理 转 https://www.cnblogs.com/Bonker/p/5417967.html...
    小小的Jobs阅读 1,394评论 0 1
  • 不足的地方请大家多多指正,如有其它没有想到的常问面试题请大家多多评论,一起成长,感谢!~ String可以被继承吗...
    启示录是真的阅读 2,937评论 3 3
  • 突然觉得,在一个环境中,人的确是会被同质化的。 原本我是一个不那么依赖手机的,但因为吃饭时旁边的人在看手机,或是你...
    克鲁迪亚阅读 289评论 0 0
  • (随感赋) 文/菊 纳章词归俊, 兰墨抒柔情; 慧聪丽质雅, 若隐且有声。 静言诗绝美, 坐镇海裳鸣; 行文韵佳律...
    斌之志阅读 427评论 14 18
  • 恨水东流 寒蝉一泯 朝三暮四 飘絮杨柳 般若菩提 乱世浮沉 一叶飘零 落叶归根 追花沃土 西风穷碧 羽扇纶巾 大漠...
    绯辰阅读 277评论 0 2