网站集约化建设 技术,优质网站建设是哪家,最火的做牛排沙拉网站,四川省建设厅官方网站电话Linq to SQL支持三种事务处理模型#xff1a;显式本地事务、显式可分发事务、隐式事务。(from MSDN: 事务 (LINQ to SQL))。MSDN中描述得相对比较粗狂#xff0c;下面就结合实例来对此进行阐述。 0. 测试环境 OSWindows Server 2008 Enterprise sp1IDEVisual Studio 2008, … Linq to SQL支持三种事务处理模型显式本地事务、显式可分发事务、隐式事务。(from MSDN: 事务 (LINQ to SQL))。MSDN中描述得相对比较粗狂下面就结合实例来对此进行阐述。 0. 测试环境 OSWindows Server 2008 Enterprise sp1IDEVisual Studio 2008, .net framework 3.5 SP1DBSQL 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: ListSQL2000.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的事件类型是“SQLBatchStarting”/“SQLBatchCompleted”从这个图中可以看到我有跟踪这个事件譬如第一个框中的Select命令汗……是MSDN上说错了 抱着这个疑问我又针对Linq to Sql SQL Server 2008做了进一步测试(这个例子也可以用来测试后面两节中的事务处理确保所有操作被分封装在同一个事务中。) 这里里面做了两件事创建一个新对象同时还修改原有记录中的值。我故意Insert可以执行成功而让Update语句执行出错如果有启用事务则出错后事务会回滚最终不会创建新记录如果没有启用事务则可以正常插入但不会执行第二步中的更新。 注意从理论上讲执行SubmitChanges时并不一定是按照上面代码的顺序先执行插入再执行更新下面是MSDN上的说法 当您进行此调用时DataContext 会设法将您所做的更改转换为等效的 SQL 命令。您可以使用自己的自定义逻辑来重写这些操作但提交顺序是由 DataContext 的一项称作“更改处理器”的服务来协调的。事件的顺序如下 当您调用 SubmitChanges 时LINQ to SQL 会检查已知对象的集合以确定新实例是否已附加到它们。如果已附加这些新实例将添加到被跟踪对象的集合。 所有具有挂起更改的对象将按照它们之间的依赖关系排序成一个对象序列。如果一个对象的更改依赖于其他对象则这个对象将排在其依赖项之后。 在即将传输任何实际更改时LINQ to SQL 会启动一个事务来封装由各条命令组成的系列。 对对象的更改会逐个转换为 SQL 命令然后发送到服务器。 此时如果数据库检测到任何错误都会造成提交进程停止并引发异常。将回滚对数据库的所有更改就像未进行过提交一样。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 Value10, 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: ListTLINQ 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: ListTLINQ 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: ListTLINQ 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里面执行的所有操作(譬如SubmitChangesExcuteCommand、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.ForEachAction(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: }