Linq to Sql : 三种事务处理方式
Linq to SQL支持三种事务处理模型:显式本地事务、显式可分发事务、隐式事务。(from MSDN: 事务 (LINQ to SQL))。MSDN中描述得相对比较粗狂,下面就结合实例来对此进行阐述。
0. 测试环境
OS | Windows Server 2008 Enterprise + sp1 |
IDE | Visual Studio 2008, .net framework 3.5 + SP1 |
DB | SQL Server 2000 + sp4 SQL Server 2008 Enterprise Edition |
1. 隐式事务
当调用SubmitChanges 时,L2S会检查当前环境是否开启了事务(下面两节中创建的事务),如果没有开始事务,则 L2S启动本地事务(IDbTransaction),并使用此事务执行所生成的 SQL 命令。
使用Reflector查看DataContext.SubmitChanges的源代码(见本文最后的附录),可以看到,如果开启了一个事务,并将其传给DataContext.Transaction,则在执行SubmitChanges时就不会再重新开启一个新的事务了。
例如,下面的代码会创建应隐式事务:
1: public static void TestTranIn2000()
2: {
3: using (SQL2000.Sql2000DataContext context1 = new SQL2000.Sql2000DataContext())
4: {
5: List<SQL2000.TLINQ> linq = context1.TLINQ.ToList();
6: linq.ForEach(e => e.Value += e.Value);
7: context1.SubmitChanges();
8: }
9: }
可以打开SQL Server Profile来查看生成的T-SQL,生成的Update语句被包含在Begin Transaction和Commit Transaction之间,如下图所示:
上图是我使用Linq to SQL + SQL Server 2000进行测试的结果。下图是我用Linq to SQL + SQL Server 2008测试的结果:
很奇怪的是,居然没有Begin Transaction和Commit Transaction了。Begin/commit trand的事件类型是“SQL:BatchStarting”/“SQL:BatchCompleted”,从这个图中可以看到,我有跟踪这个事件(譬如第一个框中的Select命令),汗……是MSDN上说错了?
抱着这个疑问,我又针对Linq to Sql + SQL Server 2008做了进一步测试(这个例子也可以用来测试后面两节中的事务处理,确保所有操作被分封装在同一个事务中。):
这里里面做了两件事:创建一个新对象,同时还修改原有记录中的值。我故意Insert可以执行成功,而让Update语句执行出错;如果有启用事务,则出错后事务会回滚,最终不会创建新记录;如果没有启用事务,则可以正常插入,但不会执行第二步中的更新。
注意:从理论上讲,执行SubmitChanges时,并不一定是按照上面代码的顺序,先执行插入再执行更新;下面是MSDN上的说法:
当您进行此调用时,DataContext 会设法将您所做的更改转换为等效的 SQL 命令。您可以使用自己的自定义逻辑来重写这些操作,但提交顺序是由 DataContext 的一项称作“更改处理器”的服务来协调的。事件的顺序如下:
此时,如果数据库检测到任何错误,都会造成提交进程停止并引发异常。将回滚对数据库的所有更改,就像未进行过提交一样。DataContext 仍具有所有更改的完整记录。 |
因此,这里还是打开SQL Server Profile来确认:
OK,现在可以放心了,这里的确是先执行Insert,再执行Update;执行Update时出现了SqlException异常。这时查看测试表TLINQ中的数据,发现没有插入新的记录进去。也就是说,这里使用了事务,但是SQL Server Profile没有跟踪到。
至于为啥会这样,我暂时也没有搞清楚;还有就是下面一节中即使执行DbConnection.BeginTransaction(),也不会跟踪到begin tran和commit tran。不知道是不是SQL Server 2008里面升级了啥。哪位如果知道原因,麻烦告知我一声,谢谢。
最后总结一下隐式事务的优缺点:
优点:使用简单,L2S都帮我们搞定了,我们不需要写任何代码。
缺点:只能处理单个DataContext中的单个SubmitChanges()函数的调用。如果需要将SubmitChanges()与其他自定义更新操作(譬如ExcuteCommand)共用一个Transaction、或者与其他DataContext共用一个DBTransation,就没辙了.......
2. 显式本地事务
SubmitChanges只能处理最基本的CUD(Create/Update/Delete)操作;而在日常的应用中,逻辑往往没有这么简单,或者考虑性能等因素,我们需要配合ADO.Net执行原生的TSQL;这时我们可能要让ADO.Net + Linq to SQL来进行配合处理。
也就是说,我们可以手工创建一个DbConnection和DbTransaction,然后传给DataContext,代码示例如下:
1: public static void TestTranInSQL2008()
2: {
3: using(SqlConnection conn = new SqlConnection(Settings.Default.AdventureWorksConnectionString))
4: {
5: conn.Open(); //1. 创建并打开DbConnection连接
6: using (SqlTransaction tran = conn.BeginTransaction()) //2. 开启DbTransaction
7: {
8: //3. 使用ADO.Net执行TSQL
9: SqlCommand cmd = new SqlCommand("Update TLinq SET Value=10", conn, tran);
10: cmd.ExecuteNonQuery();
11:
12: //4. 配合Ado.Net和Linq to Sql: 将ADO.Net的DbConnection和DbTransaction传给Linq to Sql
13: using (AdventureWorksDataContext context1 = new AdventureWorksDataContext(conn))
14: {
15: context1.Transaction = tran;
16: List<TLINQ> linq = context1.TLINQ.ToList();
17: context1.TLINQ.InsertOnSubmit(new TLINQ() { Value = "1" });
18: linq.ForEach(e => e.Value = (Convert.ToInt32(e.Value) + 1).ToString());
19: context1.SubmitChanges();
20: }
21:
22: tran.Commit(); //5. 需要提交手工创建的事务
23: }
24: }
25: }
最后总结一下使用显式本地事务的优缺点:
优点:可以配合Ado.Net一起使用,或者跨DataContext使用,实现负责的业务逻辑。
缺点:处理步骤比较繁琐。L2S中的DataContext已经提供了ExcuteCommand方法来执行原生的TSQL,这里还这样使用Ado.net就太折腾自己了.......
3. 显式可分发事务
使用TransactionScope来进行显示可分发事务控制。TransactionScope就像事务处理里面的一面魔镜,如果需要事务,就对着它喊:“主啊,请赐我事务!”,于是这个操作就有了事务功能。关于TransactionScope的详细介绍,可以参考MSDN:使用事务范围实现隐式事务,及SQL Server的联机丛书:CLR 集成和事务
使用显式可分发事务进行处理的示例代码如下:
1: public static void TestTransactionScopeInSQL2008()
2: {
3: Action action = () => //1.把要执行的操作封装在一个或多个Action中
4: {
5: using (AdventureWorksDataContext context1 = new AdventureWorksDataContext())
6: {
7: context1.ExecuteCommand("Update TLinq SET Value={0}", 10);
8: List<TLINQ> linq = context1.TLINQ.ToList();
9: context1.TLINQ.InsertOnSubmit(new TLINQ() { Value = "1" });
10: linq.ForEach(e => e.Value = (Convert.ToInt32(e.Value) + 1).ToString());
11: context1.SubmitChanges();
12: }
13: };
14: TransactioExtension.Excute(action);
15: }
或者这样:
1: /// <summary>
2: /// 此方法里面完全不必考虑事务
3: /// </summary>
4: public static void TestTransactionScopeInSQL2008()
5: {
6: using (AdventureWorksDataContext context1 = new AdventureWorksDataContext())
7: {
8: context1.ExecuteCommand("Update TLinq SET Value={0}", 10);
9: List<TLINQ> linq = context1.TLINQ.ToList();
10: context1.TLINQ.InsertOnSubmit(new TLINQ() { Value = "1" });
11: linq.ForEach(e => e.Value = (Convert.ToInt32(e.Value) + 1).ToString());
12: context1.SubmitChanges();
13: }
14: }
15:
16: //而在外面直接这样使用
17: TransactioExtension.Excute(() => TestTransactionScopeInSQL2008());
灰常灰常地简洁,把要执行的操作封装在一个或多个Action中,然后传递给TransactioExtension.Excute即可。对于封装在TransactionScope里面执行的所有操作(譬如SubmitChanges,ExcuteCommand、ExecuteQuery),最终都会解析为对ADO.NET的调用;而ADO.Net会在调用 Connection.Open 方法时自动检查Transaction.Current,并在该事务中以透明方式登记连接(除非在连接字符串中将 Enlist 关键字设置为 false)。
SqlConnection 对象的 ConnectionString 属性支持 Enlist 关键字,该关键字指示 System.Data.SqlClient 是否检测事务上下文并在分布式事务中自动登记连接。如果此关键字设置为 True(默认设置),则会在打开的线程的当前事务上下文中自动登记连接。如果此关键字设置为 False,则 SqlClient 连接不会与分布式事务交互。如果未在连接字符串中指定 Enlist,并且如果在打开相应连接时检测到一个分布式事务,则会在此分布式事务中自动登记此连接。(FROM Sql Server 2008 联机丛书)
关于TransactioExtension的封装,代码如下所示:(由于TransactionScope默认的事务隔离级别是IsolationLevel.Serializable,这里调整为ReadCommitted隔离级别,以保持与Sql Server的默认隔离级别一致):
1: public static class TransactioExtension
2: {
3: public static void Excute(params Action[] actions)
4: {
5: //使用ReadCommitted隔离级别,保持与Sql Server的默认隔离级别一致
6: Excute(IsolationLevel.ReadCommitted, null, actions);
7: }
8:
9: public static void Excute(IsolationLevel level, params Action[] actions)
10: {
11: Excute(level, null, actions);
12: }
13:
14: public static void Excute(int timeOut, params Action[] actions)
15: {
16: Excute(IsolationLevel.ReadCommitted, timeOut, actions);
17: }
18:
19: public static void Excute(IsolationLevel level, int? timeOut, params Action[] actions)
20: {
21: if (actions == null || actions.Length == 0)
22: return;
23:
24: TransactionOptions options = new TransactionOptions();
25: options.IsolationLevel = level; //默认为Serializable,这里根据参数来进行调整
26: if(timeOut.HasValue)
27: options.Timeout = new TimeSpan(0, 0, timeOut.Value); //默认60秒
28: using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required, options))
29: {
30: Array.ForEach<Action>(actions, action => action());
31: tran.Complete(); //通知事务管理器它可以提交事务
32: }
33: }
34: }
不过在使用TransactionScope时,需要注意,如果使用的数据库是SQL Server 2000,或者需要实现跨多个数据库进行事务控制,则需要开启DTC服务(位于:开始->管理工具->服务->Distributed Transaction Coordinator),下面是我的测试结果(我没有装SQL Server 2005,所以没测2005的情况):
测试环境 | 是否需要开启DTC | 创建出来的事务类型 |
Linq to Sql + Sql Server 2000(单一数据库) | 需要 | 分布式事务 |
Linq to Sql + Sql Server 2008(单一数据库) | 不需要 | 轻型本地事务 |
Linq to Sql + Sql Server 2008(跨多个数据库) | 需要 | 访问第一个数据库时,会创建轻型本地事务;当继续访问更多的数据库时,会将事务升级为完全可分发的分布式事务 |
最后总结一下使用显式可分发事务的优缺点:
优点:使用简单,可以配合ADO.Net或者DataContext.ExcuteCommand使用,可以跨DataContext使用,可以跨数据库使用,可以跨服务器使用。
缺点:分布式事务通常会使用大量的系统资源。Microsoft 分布式事务处理协调器 (MS DTC) 会管理此类事务,并集成在这些事务中访问的所有资源管理器。庆幸的是:在打开一个具有活动TransactionScope事务的连接而未打开任何其他连接的情况下,该事务会以轻型事务的形式提交,而不是产生完全分布式事务的额外开销。
最后来个汇总:
事务类型 | 优点 | 缺点 |
隐式事务 | 使用简单,由L2S自动处理。 | 仅限于单个DataContext中的SubmitChanges方法内使用。 |
显式本地事务 | 可以配合Ado.Net一起使用,可以跨多个DataContext来使用 | 使用相对繁琐一点儿;且不支持与DataContext.ExecuteCommand配合使用 |
显式可分发事务 | 功能强大(可以配合ADO.Net或者DataContext.ExcuteCommand使用,可以跨DataContext使用,可以跨数据库使用,可以跨服务器使用),使用简单 | 可能会对性能有一些影响(我暂时也没有测试过-,-) |
附:用Reflector查看DataContext.SubmitChanges的源代码如下:
1: public virtual void SubmitChanges(ConflictMode failureMode)
2: {
3: this.CheckDispose();
4: this.CheckNotInSubmitChanges();
5: this.VerifyTrackingEnabled();
6: this.conflicts.Clear();
7: try
8: {
9: this.isInSubmitChanges = true;
10: if ((Transaction.Current == null) && (this.provider.Transaction == null)) //如果不在事务环境内
11: {
12: bool flag = false;
13: DbTransaction transaction = null;
14: try
15: {
16: if (this.provider.Connection.State == ConnectionState.Open)
17: {
18: this.provider.ClearConnection();
19: }
20: if (this.provider.Connection.State == ConnectionState.Closed)
21: {
22: this.provider.Connection.Open();
23: flag = true;
24: }
25: transaction = this.provider.Connection.BeginTransaction(IsolationLevel.ReadCommitted); //开启事务
26: this.provider.Transaction = transaction;
27: new ChangeProcessor(this.services, this).SubmitChanges(failureMode);
28: this.AcceptChanges();
29: this.provider.ClearConnection();
30: transaction.Commit();
31: return;
32: }
33: catch
34: {
35: if (transaction != null)
36: {
37: try
38: {
39: transaction.Rollback();
40: }
41: catch
42: {
43: }
44: }
45: throw;
46: }
47: finally
48: {
49: this.provider.Transaction = null;
50: if (flag)
51: {
52: this.provider.Connection.Close();
53: }
54: }
55: }
56: new ChangeProcessor(this.services, this).SubmitChanges(failureMode);
57: this.AcceptChanges();
58: }
59: finally
60: {
61: this.isInSubmitChanges = false;
62: }
63: }
Linq to Sql : 三种事务处理方式的更多相关文章
- SQL、LINQ、Lambda 三种用法(转)
SQL.LINQ.Lambda 三种用法颜色注释: SQL LinqToSql Lambda QA1. 查询Student表中的所有记录的Sname.Ssex和Class列.select sname, ...
- SQL Server中的三种Join方式
1.测试数据准备 参考:Sql Server中的表访问方式Table Scan, Index Scan, Index Seek 这篇博客中的实验数据准备.这两篇博客使用了相同的实验数据. 2.SQ ...
- Asp.Net中的三种分页方式
Asp.Net中的三种分页方式 通常分页有3种方法,分别是asp.net自带的数据显示空间如GridView等自带的分页,第三方分页控件如aspnetpager,存储过程分页等. 第一种:使用Grid ...
- Entity Framework 5.0系列之EF概览-三种编程方式
概述 在开发面向数据的软件时我们常常为了解决业务问题实体.关系和逻辑构建模型而费尽心机,ORM的产生为我们提供了一种优雅的解决方案.ADO.NET Entity Framework是.NET开发中一种 ...
- 【jdbc】【c3p0】c3p0三种配置方式【整理】
c3p0三种配置方式 c3p0的配置方式分为三种,分别是1.setters一个个地设置各个配置项2.类路径下提供一个c3p0.properties文件3.类路径下提供一个c3p0-config.xml ...
- 通过三个DEMO学会SignalR的三种实现方式
一.理解SignalR ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息 ...
- Hive metastore三种配置方式
http://blog.csdn.net/reesun/article/details/8556078 Hive的meta数据支持以下三种存储方式,其中两种属于本地存储,一种为远端存储.远端存储比较适 ...
- tomcat下jndi的三种配置方式
jndi(Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API.命名服务将名称和对象联系起来,使得我们可以用 ...
- 【转】tomcat下jndi的三种配置方式
jndi(Java Naming and Directory Interface,Java命名和目录接口)是一组在Java应用中访问命名和目录服务的API.命名服务将名称和对象联系起来,使得我们可以用 ...
随机推荐
- 2014.06.14 GlusterFS技术交流视频
6月14线下GlusterFS视频交流.高清视频是非常好的,我初听言论方面,谈到迅速,似乎不是很清楚,讲座结束后速度需要改进.谢谢能力的天空AbleSky高大内设,谢谢学生参加. 在线公开课:http ...
- UML造型——使用EA时序图工具的开发实践和经验
Enterprise Architect watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGlhb3l3NzE=/font/5a6L5L2T/fontsiz ...
- Sphinx/Coreseek 4.1 跑 buildconf.sh 一个错误,无法生成configure档
安装前 coorseek 什么时候,遇到一些错误.该官方网站无法看到的解决方案,终于 google 在大牛的博客评论区找到一个解决方案.突然跑到他的膝盖介绍~~ 这里整理是为了方便一些人发现,墙毕竟让 ...
- 本文摘录 - Infobright
背景 论文 Brighthouse: AnAnalytic Data Warehouse for Ad-hoc Queries.VLDB 2008 brighthouse它是一个面向列的数据仓库.在数 ...
- poj 1456 Supermarket(并查集维护区间)
题意:有一些货物,每一个货物有价值和卖出的截至日期,每天能够卖一个货物,问能卖出的最大价值是多少. 思路:算法不难想到,按价值降序排列.对于每一件货物,从deadline那天開始考虑.假设哪天空 ...
- 83. 从视图索引说Notes数据库(上)
索引是数据库系统重要的feature,不管是传统的关系型数据库还是时兴的NoSQL数据库,它攸关查询性能,因而在设计数据库时须要细加考量.然而,Lotus Notes隐藏技术底层.以用户界面为导向.追 ...
- Javascript中的深拷贝和浅拷贝
var obj = { a:1, arr: [1,2] }; var obj1 = obj; //浅复制 var obj2 = deepCopy(obj); //深复制 javascript中创建对象 ...
- ORA-12638: 无法检索身份证明 解决的方法
the NTS option makes the Oracle client attempt to use your current Windows domain credentials to aut ...
- hdu 4445 Crazy Tank (暴力枚举)
Crazy Tank Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- Java设计模式(七)策略模式 模板模式
(十三)策略模式 策略图案限定了多个封装算法,该算法可以相互替换包.法的客户.借用还有一位大神的样例. interface ICalculator{ public int calculate(Stri ...