Linq to Sql : 三种事务处理方式

    Linq to SQL支持三种事务处理模型:显式本地事务、显式可分发事务、隐式事务。(from  MSDN: 事务 (LINQ to SQL))。MSDN中描述得相对比较粗狂,下面就结合实例来对此进行阐述。

0. 测试环境

OSWindows Server 2008 Enterprise + sp1
IDEVisual Studio 2008, .net framework 3.5 + SP1
DBSQL 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之间,如下图所示:

L2S_SQL2000_Tran

    上图是我使用Linq to SQL + SQL Server 2000进行测试的结果。下图是我用Linq to SQL + SQL Server 2008测试的结果:

L2S_SQL2008_Tran

    很奇怪的是,居然没有Begin Transaction和Commit Transaction了。Begin/commit trand的事件类型是“SQL:BatchStarting”/“SQL:BatchCompleted”,从这个图中可以看到,我有跟踪这个事件(譬如第一个框中的Select命令),汗……是MSDN上说错了?

    抱着这个疑问,我又针对Linq to Sql + SQL Server 2008做了进一步测试(这个例子也可以用来测试后面两节中的事务处理,确保所有操作被分封装在同一个事务中。):

L2S_TranInSQL20081

    这里里面做了两件事:创建一个新对象,同时还修改原有记录中的值。我故意Insert可以执行成功,而让Update语句执行出错;如果有启用事务,则出错后事务会回滚,最终不会创建新记录;如果没有启用事务,则可以正常插入,但不会执行第二步中的更新。

    注意:从理论上讲,执行SubmitChanges时,并不一定是按照上面代码的顺序,先执行插入再执行更新;下面是MSDN上的说法:

    当您进行此调用时,DataContext 会设法将您所做的更改转换为等效的 SQL 命令。您可以使用自己的自定义逻辑来重写这些操作,但提交顺序是由 DataContext 的一项称作“更改处理器”的服务来协调的。事件的顺序如下:

  1. 当您调用 SubmitChanges 时,LINQ to SQL 会检查已知对象的集合以确定新实例是否已附加到它们。如果已附加,这些新实例将添加到被跟踪对象的集合。

  2. 所有具有挂起更改的对象将按照它们之间的依赖关系排序成一个对象序列。如果一个对象的更改依赖于其他对象,则这个对象将排在其依赖项之后。

  3. 在即将传输任何实际更改时,LINQ to SQL 会启动一个事务来封装由各条命令组成的系列。

  4. 对对象的更改会逐个转换为 SQL 命令,然后发送到服务器。

    此时,如果数据库检测到任何错误,都会造成提交进程停止并引发异常。将回滚对数据库的所有更改,就像未进行过提交一样。DataContext 仍具有所有更改的完整记录。

    因此,这里还是打开SQL Server Profile来确认:
L2S_TranInSQL20082

    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: }

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439225.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode - 33】搜索旋转排序数组(二分)

力扣 解题报告&#xff1a; 二分。但是有不少细节要考虑清楚。 所以干脆考虑另一种二分的方式。也就是第二次二分的时候&#xff0c;把两半数组给拼成一个完整的数组&#xff0c;当然下标需要是虚拟的&#xff0c;这一步可以用偏移量取模完成。这样就不需要考虑边界情况了。 …

SHAREPOINT - CAML列表查询

首先要了解的是CAML(Collaboration Application Markup Language)不仅仅是用在对列表、文档库的查询&#xff0c;字段的定义&#xff0c;站点定义等处处使用的都是CAML。 简单的提一下CAML列表查询相关知识&#xff0c;请注意CAML查询无论对于标签还是值均大小写敏感 CAML查询…

【LeetCode - 1765】. 地图中的最高点

力扣 解题报告&#xff1a; 多元BFS。 进阶一下&#xff1a; 二维数组&#xff0c;1表示等高线&#xff0c;0表示平地&#xff0c;比如 输入 010 111 010 输出 010 121 010输入 010 101 010 输出 010 111 010即输入一个二维地图&#xff0c;保证等高线一定是闭合的环&#x…

Linq找不到行或行已更改

1.debug确认&#xff0c;待修改记录已经正确定位&#xff0c;各字段均已正确赋值 2.最后发现原来是Linq使用的表实际中有个字段&#xff0c;数据库中为Nullable&#xff0c;而dbml是Not Nullable&#xff0c;二者不一致 一个逻辑是&#xff0c;我现在填写的是非空值&#xff…

【LeetCode - 32】最长有效括号

给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 示例 1&#xff1a; 输入&#xff1a;s "(()" 输出&#xff1a;2 解释&#xff1a;最长有效括号子串是 "()" 示例 2&#xff1a; 输入…

【转】微服务架构下分布式事务方案

1 微服务的发展 微服务倡导将复杂的单体应用拆分为若干个功能简单、松耦合的服务&#xff0c;这样可以降低开发难度、增强扩展性、便于敏捷开发。当前被越来越多的开发者推崇&#xff0c;很多互联网行业巨头、开源社区等都开始了微服务的讨论和实践。Hailo有160个不同服务构成…

【LeetCode - 443】压缩字符串(模拟)

解题报告&#xff1a; 直接模拟。 class Solution { public:int compress(vector<char>& chars) {int p 0;for(int i 0; i<chars.size();) {int j i1;while(j<chars.size() && chars[j] chars[i]) j;chars[p] chars[i];if(j-i > 1) {int cnt…

Linq to SQL之使用事务

事务是一个原子的工作单位&#xff0c;必须完整的完成单位里的所有工作&#xff0c;要么全部执行&#xff0c;要么全部都不执行。如果提交事务&#xff0c;则事务执行成功&#xff1b;如果回滚事务&#xff0c;则事务执行失败。 事务具备4个基本特性--ACID(原子性、一致性、孤立…

【LeetCode - 798】得分最高的最小轮调(转化法)

解题报告&#xff1a; 思路一&#xff1a;这题首先说一个nlogn的方法。 首先一个主客转化&#xff0c;题目描述是说把数组做翻转&#xff0c;idx不变&#xff0c;然后nums[i]和i作比较。那么我们可以转化为让数组不变&#xff0c;idx转变&#xff0c;即&#xff1a;假设刚开始…

【转】聊聊分布式事务,再说说解决方案

前言 最近很久没有写博客了&#xff0c;一方面是因为公司事情最近比较忙&#xff0c;另外一方面是因为在进行 CAP 的下一阶段的开发工作&#xff0c;不过目前已经告一段落了。 接下来还是开始我们今天的话题&#xff0c;说说分布式事务&#xff0c;或者说是我眼中的分布式事务…

【LeetCode - 2049】统计最高分的节点数目

解题报告&#xff1b; 直接dp。注意mx也得longlong AC代码&#xff1a; class Solution { public:vector<int> vv[200005];int sum[200005];long long ans[200005];int n;void dfs(int x) {ans[x] 1; sum[x] 1;for(int i 0; i<vv[x].size(); i) {dfs(vv[x][i]);s…

Web Service 实现分布式事务

首先要声明&#xff0c;不推荐在web service中实现分布式事务。 原因如下&#xff1a;   1、webservice在通信层上是一种无连接的协议&#xff0c;每两次调用之间&#xff0c;tcp连接是断开的。而分布式事务需要保存事务上下文&#xff0c;这是一个难点   2、web service是…

【LeetCode每日一题】2024. 考试的最大困扰度

​​​​​​力扣 解题报告&#xff1a; 因为只有T和F两个元素&#xff0c;不难证明单向性。尺取法解决。当然这题也可以二分。 AC代码&#xff1a; class Solution { public:int maxConsecutiveAnswers(string answerKey, int k) {int l 0, r 0;int T 0, F 0;int ans …

使用WebService如何实现分布式事务

在 System.EnterpriseServices 名称空间中实现了COM服务的相关类&#xff0c;其中就提供事务支持。 你可以在你的方法上加上TransactionAttribute特性标记&#xff0c;那个方法就支持事务了。 然后在那个方法中就可以用ContextUtil.SetComplete()和ContextUtil.SetAbort()方法分…

【Leetcode - 172】阶乘后的零(思维)

给定一个整数 n &#xff0c;返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * ... * 3 * 2 * 1 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;0 解释&#xff1a;3! 6 &#xff0c;不含尾随 0 示例 2&#xff1a; 输入&#xff1a;n 5 输出&…

linq、lambda、entity framework之间的关系

lambda&#xff1a; 一种匿名委托的精简版写法&#xff0c;明显的标志是>符号 entity framework&#xff08;简称EF&#xff09;&#xff1a; 微软访问数据库的最快捷最成熟的框架&#xff08;ORM&#xff09;&#xff0c;在EF出现以前有SqlHelper、NHibernate等访问数据库…

2022-08-20-网易笔试题

写在前面 题目收集来源自网络&#xff0c;前四题是开发岗的&#xff0c;后四题是算法岗的&#xff0c;因为代码无处提交&#xff0c;不一定正确&#xff0c;就不贴出来了&#xff0c;这里只写一下我的思路吧~欢迎大家一起讨论~~ 1、 思路&#xff1a;因为最大1e9&#xff0c…

TUN/TAP设备浅析(一) -- 原理浅析

TUN/TAP设备浅析 TUN设备 TUN 设备是一种虚拟网络设备&#xff0c;通过此设备&#xff0c;程序可以方便地模拟网络行为。TUN 模拟的是一个三层设备,也就是说,通过它可以处理来自网络层的数据&#xff0c;更通俗一点的说&#xff0c;通过它&#xff0c;我们可以处理 IP 数据包…

2022-08-21 星环科技-C++开发笔试

1、 思路&#xff1a;拓扑排序&#xff0c;不解释了 2、 思路&#xff1a; 本来以为他是一个图论问题&#xff0c;找最大环。 但其实对于这种情况&#xff0c;他是要输出0的&#xff0c;而不是9&#xff0c;所以他不是一个图论问题&#xff0c;他带有顺序性&#xff0c;这种可…

TUN/TAP设备浅析(二) -- TUN/TAP的编程

这篇文章想详细阐述一下有关于 TUN/TAP 设备的编程。 其实关于这两种设备的编程&#xff0c;基本上属于八股文&#xff0c;大家一般都这么干。 启动设备之前 有的linux 并没有将tun 模块编译到内核之中&#xff0c;所以&#xff0c;我们要做的第一件事情就是检查我们的系统是…