热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

在.NET2.0中使用自定义事务操作

在.NET2.0中使用自定义事务操作
  .net 2.0 framework 中新增了 System.Transactions 命名空间,其中提供的一系列接口和类使得在.net 2.0 中使用事务比起从前要方便了许多。有关在 .net 2.0 下操作数据库事务的文章已经有了很多,这里只提一下如何设计自定义事务操作。

  一、事务使用基础

  先看一段使用事务的代码:

1using (TransactionScope ts= new TransactionScope())
2{
3 //自定义操作
4 ts.Complete();
5}

  这里使用 using 语句定义了一段隐性事务。如果我们在该语句块中加入一段对 SQL Server 操作的代码,那么它们将会自动加入这个事务。可以看出,这种事务的使用方式是极其方便的。

  那么,有没有可能在该语句块中加入我们自己定义的事务操作,并且该操作能够随着整个事务块的成功而提交,随其失败而回滚呢?答案当然是可以的,否则我就不会写这篇随笔了。

  二、实现自定义事务操作

  根据事务的特性,我们可以推想:这个操作必须有实现提交和回滚之类动作的方法。没错,这就是 System.Transactions 命名空间中的 IEnlistmentNotification 接口。我们先写一个最简单的实现:

1class SampleEnlistment1 : IEnlistmentNotification
2{
3 void IEnlistmentNotification.Commit(Enlistment enlistment)
4 {
5 Console.WriteLine("提交!");
6 enlistment.Done();
7 }
8
9 void IEnlistmentNotification.InDoubt(Enlistment enlistment)
10 {
11 throw new Exception("The method or operation is not implemented.");
12 }
13
14 void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
15 {
16 Console.WriteLine("准备!");
17 preparingEnlistment.Prepared();
18 }
19
20 void IEnlistmentNotification.Rollback(Enlistment enlistment)
21 {
22 Console.WriteLine("回滚!");
23 enlistment.Done();
24 }
25}
26
27

  好,定义完之后,还需要向事务管理器进行注册,把它加入到当前事务中去:

1using (TransactionScope ts= new TransactionScope())
2{
3 SampleEnlistment1 myEnlistment1 = new SampleEnlistment1();
4 Transaction.Current.EnlistVolatile(myEnlistment1, EnlistmentOptions.None);
5 ts.Complete();
6}

  执行这一段代码,我们可以得到以下的输出:

  准备!
  提交!

  先解释一下,当调用 ts.Complete() 方法的时候,表示事务已成功执行。随后,事务管理器就会寻找当前所有已注册的条目,也就是 IEnlistmentNotification 的每一个实现,依次调用它们的 Prepare 方法,即通知每个条目做好提交准备,当所有条目都调用了 Prepared() 表示自己已经准备妥当之后,再依次调用它们的 Commit 方法进行提交。如果其中有一个没有调用 Prepared 而是调用了 ForceRollback 的话,整个事务都将回滚,此时事务管理器再调用每个条目的 Rollback 方法。

  而如果我们将前面的 ts.Complete() 行注释掉,显然执行结果就将变为:

  回滚!

  三、一个实现赋值的自定义操作

  考虑一下,我们要实现一个事务赋值操作。该如何做法?以下是一个例子:

1class SampleEnlistment2 : IEnlistmentNotification
2{
3 public SampleEnlistment2(AssignTransactionDemo var, int newValue)
4 {
5 _var = var;
6 _oldValue = var.i;
7 _newValue = newValue;
8 }
9
10 private AssignTransactionDemo _var;
11 private int _oldValue;
12 private int _newValue;
13
14 void IEnlistmentNotification.Commit(Enlistment enlistment)
15 {
16 _var.i = _newValue;
17 Console.WriteLine("提交!i的值变为:" + _var.i.ToString());
18 enlistment.Done();
19 }
20
21 void IEnlistmentNotification.InDoubt(Enlistment enlistment)
22 {
23 throw new Exception("The method or operation is not implemented.");
24 }
25
26 void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
27 {
28 preparingEnlistment.Prepared();
29 }
30
31 void IEnlistmentNotification.Rollback(Enlistment enlistment)
32 {
33 _var.i = _oldValue;
34 Console.WriteLine("回滚!i的值变为:" + _var.i.ToString());
35 enlistment.Done();
36 }
37}
38
39class AssignTransactionDemo
40{
41 public int i;
42
43 public void AssignIntVarValue(int newValue)
44 {
45 SampleEnlistment2 myEnlistment2 = new SampleEnlistment2(this, newValue);
46 Guid guid = new Guid("{3456789A-7654-2345-ABCD-098765434567}");
47 Transaction.Current.EnlistDurable(guid, myEnlistment2, EnlistmentOptions.None);
48 }
49}
50
51

  然后,这样来使用:

1AssignTransactionDemo atd = new AssignTransactionDemo();
2atd.i = 0;
3using (TransactionScope scope1 = new TransactionScope())
4{
5 atd.AssignIntVarValue(1);
6 Console.WriteLine("事务完成!");
7 scope1.Complete();
8 Console.WriteLine("退出区域之前,i的值为:" + atd.i.ToString());
9}
10Thread.Sleep(1000);
11Console.WriteLine("退出区域之后,i的值为:" + atd.i.ToString());

  运行这一段代码,我们可以看到如下结果:

  事务完成!
  退出区域之前,i的值为:0
  提交!i的值变为:1
  退出区域之后,i的值为:1

  从输出结果来看,赋值操作被成功执行了。可是有没有感觉有些奇怪?先做个讨论:

  1、如果前面没有 Thread.Sleep(1000) 这一行,那么我们多半会看到最后一行的输出中,i 的值依然会是 0!为什么?想想就容易明白,这里对 Commit 方法是采用的异步调用,如同另开了一个线程。如果主线程不作等待的话,当输出的时候事务的 Commit 方法多半还没有被执行,输出的结果当然就会不对。

  2、这个例子中,赋值操作是在 Commit 方法中才实际执行的。但实际上就本例而言,我们也可以做个调整:将赋值操作放在 AssignIntVarValue 方法的最后去执行,然后把 Commit 方法中的赋值操作去掉。相关的代码变化如下:

1class SampleEnlistment2 : IEnlistmentNotification
2{
3 void IEnlistmentNotification.Commit(Enlistment enlistment)
4 {
5 enlistment.Done();
6 }
7 //其它略
8}
9
10class AssignTransactionDemo
11{
12 public int i;
13
14 public void AssignIntVarValue(int newValue)
15 {
16 SampleEnlistment2 myEnlistment2 = new SampleEnlistment2(this, newValue);
17 Guid guid = new Guid("{3456789A-7654-2345-ABCD-098765434567}");
18 Transaction.Current.EnlistDurable(guid, myEnlistment2, EnlistmentOptions.None);
19 i = newValue;
20 Console.WriteLine("提交前改变!i的值为:" + i.ToString());
21 }
22}
23
24

  这样,执行结果将会变为:

  提交前改变!i的值为:1
  事务完成!
  退出区域之前,i的值为:1
  退出区域之后,i的值为:1

  3、在前面的基础上,当把调用的地方作如下改动,使事务失败:

1using (TransactionScope scope1 = new TransactionScope())
2{
3 atd.AssignIntVarValue(1);
4 Console.WriteLine("事务失败!");
5 //scope1.Complete();
6 Console.WriteLine("退出区域之前,i的值为:" + atd.i.ToString());
7}

  此时的执行结果将变为:

  提交前改变!i的值为:1
  事务失败!
  退出区域之前,i的值为:1
  回滚!i的值变为:0
  退出区域之后,i的值为:0

  可见,事务已成功回滚。

  四、进一步的讨论

  前面我们都是只进行了一次赋值操作,如果我们需要进行两次呢?

1using (TransactionScope scope1 = new TransactionScope())
2{
3 atd.AssignIntVarValue(1);
4 atd.AssignIntVarValue(2);
5 Console.WriteLine("事务失败!");
6 //scope1.Complete();
7 Console.WriteLine("退出区域之前,i的值为:" + atd.i.ToString());
8}

  这时的执行结果将会是如何?我们当然是希望回滚的时候,i 的值能先变回为 1,再变回为 0。但是实际结果呢?

  提交前改变!i的值为:1
  提交前改变!i的值为:2
  事务失败!
  退出区域之前,i的值为:2
  回滚!i的值变为:0
  回滚!i的值变为:1
  退出区域之后,i的值为:1

  显然,事务的回滚并没有按照我们希望的顺序来,是何原因?分析一下机制就能知道,事务管理器向每个条目发出回滚命令的时候只是发出了一个异步调用,并且很可能还是按登记的顺序来发出的,这样一来,Rollback 方法的调用顺序显然就不能保证了。

  这时,如果将 Rollback 方法作一个小调整:

1void IEnlistmentNotification.Rollback(Enlistment enlistment)
2{
3 while (_var.i != _newValue)
4 {
5 Thread.Sleep(500);
6 }
7 _var.i = _oldValue;
8 Console.WriteLine("回滚!i的值变为:" + _oldValue.ToString());
9 enlistment.Done();
10}

  再次运行之,结果就对了:

  提交前改变!i的值为:1
  提交前改变!i的值为:2
  事务失败!
  退出区域之前,i的值为:2
  回滚!i的值变为:1
  回滚!i的值变为:0

  结果的正确其实并不是调用的顺序就对了,只是 Rollback 方法在执行的时候先检查一下 _newValue 的值是否与当前 i 的值一致,不一致的话就等上一会儿。在等待的过程中,另一个实例的 Rollback 方法被执行,而它检查发现是匹配的,所以就会回滚到 1。第一个 Rollback 等待结束后再检查发现匹配了,于是就回滚为 0。

  当然实际应用中,这种方法是极不可取的。且不说执行顺序依然会有很大的风险,光是设计方式就有大问题。那么在实际应用中我们应当如何去做呢?这里只提供一下设计思想,具体的实现代码不再列出了。

  在前面的例子中,两次赋值共进行了两次登记,这一点是引发不稳定性的起因。我们应当考虑,两次赋值依然只登记一次,在第一次赋值的时候,建立一个 SampleEnlistment2 的实例并在 AssignTransactDemo 中保存下来,并且 SampleEnlistment2 需要记录当前的操作。下一次赋值时,仍然使用这个实例,只进行操作记录即可。这样,当回滚的时候,它根据记录的反顺序执行回滚操作就可以了。

  再进一步呢?如果说有多个 Transaction 需要进行赋值操作呢?这时我们可以在 AssignTransactionDemo 类中加入一个 Dictionary,使用的时候根据 Transaction 去寻找相应的条目即可。

  本文讨论暂到此为止。在微软的101个例子中,有一个使用事务进行文件拷贝的例子。那里面有比较深入的实现。如果你还没有看过,推荐去研究一下,相信你读过此篇随笔,研究它应当不再是个难题。
推荐阅读
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 本文介绍了在SQL中查询分组后每组行数的统计方法。通过使用count()函数和GROUP BY子句可以统计每组的行数,但是如何统计所有组的行数呢?本文提供了一种实现方法,并给出了相应的SQL查询语句。 ... [详细]
  • 在数据分析工作中,我们通常会遇到这样的问题,一个业务部门由若干业务组构成,需要筛选出每个业务组里业绩前N名的业务员。这其实是一个分组排序的 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
  • 本文介绍了adg架构设置在企业数据治理中的应用。随着信息技术的发展,企业IT系统的快速发展使得数据成为企业业务增长的新动力,但同时也带来了数据冗余、数据难发现、效率低下、资源消耗等问题。本文讨论了企业面临的几类尖锐问题,并提出了解决方案,包括确保库表结构与系统测试版本一致、避免数据冗余、快速定位问题等。此外,本文还探讨了adg架构在大版本升级、上云服务和微服务治理方面的应用。通过本文的介绍,读者可以了解到adg架构设置的重要性及其在企业数据治理中的应用。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
author-avatar
逍遥子2502897751
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有