开篇
什么是事务,事务的应用场景
做项目时,经常会遇到一些需求,比如注册用户时,要求同时存入用户的基本信息和初始化该用户的帐户,如果在这两个环节中的任何一个地方出错,则要求回滚所有操作,这就是事务,它的主要目的是为了数据的完整性,即要么全盘成功,要么全盘失败,大家都穿一条裤衩。
今天我要说的复杂事务
事务的概念很简单,应用起来也很方便,当然可以在存储过程中直接实现事务保证数据的完整性,但是我一般不会这样做,因为我基本把数据库就当做一个数据仓库而已,它是一个容器,没有其他逻辑,逻辑都放领域层来处理,因为这不是我们今天的主要话题,所以就不多讲了。回到正题,那么我们要在C#中实现事务是怎么实现的呢,很简单,直接调用Connection的BeginTransaction方法即可,我们拿ADO.Net中的SqlServer举例,完整的应用示例如下:
以上代码两Usr表中插入两条数据,要求这两条数据必须都成功插入,否则回滚,逻辑很简单。
更好的方式
上面的代码没什么问题,但是如果我们每个地方都这样写,那的确很繁琐,所以出来了很多ORM框架来辅助我们,再看一下EntityFramework是怎么实现事务的
代码:
using (RetailEntities context = new RetailEntities()) { context.Customers.Add(entity1); context.Customers.Add(entity2); context.SaveChanges(); }
估计大家都用过,没错,它在SaveChanges方法之前,自动为我们开始了一个事务。这样做的好处,不言而喻,比起上面那样做可谓是简洁了许多。但是今天我们不使用EntityFramework,而是使用自己的方式来解决将要面临的问题。
我实现事务的方式
看过我博客的朋友有可能会知道,我以前也写过一个ORM框架(XDBFramework),今天我们主要不讲这个框架,但会引用其中的代码来说明问题,下面就上代码,来看在XDBFramework中是怎样实现事务的。
DataContext类
DataAccess类,也就是上面的_da
生成的DataContext类
调用代码:
using (var context = new DataContext()) { long count = context.Admin.Count(); var a = DataContextStatic.Recharge.Count(s => s.State == Convert.ToInt32(1)); string passport = "x" + DateTime.Now.Ticks; context.BeginTransaction(); try { context.Admin.Insert(new Model_Admin { Passport = passport, Password = "123456", AddTime = DateTime.Now }); context.Admin.Insert(new Model_Admin { Passport = passport + "_2", Password = "123456", AddTime = DateTime.Now }); context.CommitTransaction(); } catch { context.RollbackTransaction(); } }
能这样实现的关键在于DataContext对象拥有一个DataAccess对象,调用各个DataContext上的各个实体操作自身对应表的时候会调用同一个DataAccess对象,也就是同一个connection,同一个trasaction进行数据的检索,插入,更新,删除操作。这样就实现了把多个操作放到一个事务内。
引出问题
想象一下这样一种场景,用户在购买一件商品时,既要生成订单,又要扣除其帐户的相应金额,我们假定在生成订单时会生成多个明细,扣除余额时也会有多条明细。
解决办法:
1.有一种方式就是建立一个manager类,然后把生成订单和扣除其帐户的相应金额的逻辑写到一个事务放到这个manager中,如果采用这种方式,当然就不会有本文存在了。
2.这里要以领域模型的思维方式,即什么对象做什么事,以领域模型建模,这里就有两个模型,即用户帐户和订单。
于是:
a.订单实体里有生成订单的方法,这个方法中包含插入订单总览和明细。
b.帐户这个实体里有扣除余额的方法,这个方法中包含扣除余额和扣除明细
这样一来,就有好几个需要事务,即:
1,生成订单和扣除余额
2,生成订单和明细
3,扣除余额和扣除明细
这样问题就来了,它们本身是一个事务,为了模型的完整,我们现在将它们放在了三处。如何在最后将它们合并成一个事务呢,这就是我们今天要解决的问题。
进入实例
场景
工作流大家应该接触过,我现在就拿这个来说,在保存设计图时,我需要保存那些节点和线条的位置和大小等,在这之前如果是一副被修改的流程图我还要清除原来的元素,在这个场景中,有很多操作都要求在一个事务中,比如之前元素的清徐,新节点的保存,新连接线的保存,新注释的保存等。而这里又有很多实体,比如流程图模版,活动节点(用户活动,系统活动),连接线,注释等,拿它们的保存方法来讲都有不同逻辑,甚至在保存方法内也还有事务的存在。
代码
流程图的保存方法
可以看到这里开始了一个大的事务,将各元素的清除和保存都放到了这个事务中。
以下的删除方法中又包含了事务(以"Transaction(()=>)"开始的地方)
实现
总结
主要思想是为每个线程分配一个DataContext对象,如果发现是同一个线程在开启事务时,判断是否已经开启事务,如是则直接使用已经开启的事务,如否则开启一个新的事务,原理很简单。