第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges、DBContextTransaction、TransactionScope)...

本文详细介绍了Entity Framework (EF) 中的事务处理方式,包括SaveChanges、DbContextTransaction和TransactionScope三种主要事务类型及其应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 什么是事务

   我们通俗的理解事务就是一系列操作要么全部成功、要么全部失败(不可能存在部分成功,部分失败的情况)。

   举一个事务在我们日常生活中的经典例子:两张银行卡(甲、乙),甲向乙转钱,整个过程需要执行两个操作,甲账户钱减少,乙账户钱增加,这是转账成功的情况;转账失败时候,二者均不执行,甲乙账户钱都不变。

1. SQLServer中的事务

  SQLServer中的事务具体封装方式有多种,但无论是哪种,都需要依赖于这三句代码:begin transaction 、commit transaction、rollback transaction。 

   具体怎么使用,详见数据库事务章节:去复习

2. EF中的事务

   EF中的事务主要分为三类,分别是SaveChanges、DbContextTransaction、TransactionScope。

   需要掌握的是这三种事务各自的使用方式和适用场景。

二. SaveChanges

   SaveChanges是EF中最常见的事务了,在前面章节,多次提到,SaveChanges一次性将本地缓存中所有的状态变化一次性提交到数据库,这就是一个事务,要么统一成功,要么统一回滚。

   应用场景:该种事务模式在实际开发中非常常用,在处理模块业务同时需要对一张表(或多张表)进行一系列增删改操作,但这些操作都是要么都成功,要么都失败,这种情况下可以在流程的最后 调用SaveChanges,一次性保存本地属性状态的全部变化。

  该模式作用于一个DBContext,即一个数据库的EF的上下文,不能控制多个数据库。

    比如:注册场景,可能需要向用户表、日志表、账户表等多张表中插入数据,其中一张表插入失败,所有的都需要回滚,这种情况,就可以在该业务的最后,统一调用SaveChanges一次性的事务提交。

1. 事务成功

 1      private static void TestSaveChanges()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 //增加
 6                 TestInfor t1 = new TestInfor()
 7                 {
 8                     id = Guid.NewGuid().ToString("N"),
 9                     txt1 = "txt1",
10                     txt2 = "txt2"
11                 };
12                 db.Set<TestInfor>().Add(t1);
13                 //删除
14                 TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "1").FirstOrDefault();
15                 if (t2 != null)
16                 {
17                     db.Set<TestInfor>().Remove(t2);
18                 }
19                 //修改
20                 TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault();
21                 t3.txt2 = "我是李马茹23";
22 
23                 //SaveChanges事务提交
24                 int n = db.SaveChanges();
25                 Console.WriteLine("数据作用条数:" + n);
26             }
27         }    

2. 事务回滚

 1       private static void TestSaveChangesFailure()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 //增加
 6                 TestInfor t1 = new TestInfor()
 7                 {
 8                     id = Guid.NewGuid().ToString("N"),
 9                     txt1 = "txt1",
10                     txt2 = "txt2"
11                 };
12                 db.Set<TestInfor>().Add(t1);
13                 //删除
14                 TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "1").FirstOrDefault();
15                 if (t2 != null)
16                 {
17                     db.Set<TestInfor>().Remove(t2);
18                 }
19                 //修改
20                 TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault();
21                 t3.txt2 = "我是李马茹23";
22                 //增加(制造错误)
23                 TestInfor t4 = new TestInfor()
24                 {
25                     id = Guid.NewGuid().ToString("N") + "123",
26                     txt1 = "txt1",
27                     txt2 = "txt2"
28                 };
29                 db.Set<TestInfor>().Add(t4);
30 
31                 //SaveChanges事务提交
32                 int n = db.SaveChanges();
33                 Console.WriteLine("数据作用条数:" + n);
34             }
35         }

三. DbContextTransaction事务

  该事务为EF6新增的事务,通常用于手动接管事务,某些操作是一个事务,某些操作是另外一个事务。

  使用场景:EF调用SQL语句的时候使用该事务、 多个SaveChanges的情况(解决自增ID另一个业务中使用的场景)。

  核心代码:BeginTransaction、Commit、Rollback、Dispose. 如果放到using块中,就不需要手动Dispose了。

  该种事务与数据库中的transaction事务原理一致,在EF中,由于每次调用 db.Database.ExecuteSqlCommand(sql1, pars1)的时候,即刻执行了该SQL语句,所以要把他放到一个大的事务中,整体提交、回滚.(EF6新增的)

1. 事务成功

 1      private static void TestDbContextTransaction()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 DbContextTransaction trans = null;
 6                 try
 7                 {
 8                     //开启事务
 9                     trans = db.Database.BeginTransaction();
10                     //增加
11                     string sql1 = @"insert into TestInfor  values(@id,@txt1,@txt2)";
12                     SqlParameter[] pars1 ={
13                                                  new SqlParameter("@id",Guid.NewGuid().ToString("N")),
14                                                  new SqlParameter("@txt1","txt11"),
15                                                  new SqlParameter("@txt2","txt22")
16                                             };
17                     db.Database.ExecuteSqlCommand(sql1, pars1);
18                     //删除
19                     string sql2 = @"delete from TestInfor where id=@id";
20                     SqlParameter[] pars2 ={
21                                                  new SqlParameter("@id","22")
22                                             };
23                     db.Database.ExecuteSqlCommand(sql2, pars2);
24                     //修改
25                     string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";
26                     SqlParameter[] pars3 ={
27                                                  new SqlParameter("@id","3"),
28                                                  new SqlParameter("@txt1","二狗子")
29                                             };
30                     db.Database.ExecuteSqlCommand(sql3, pars3);
31 
32                     //提交事务
33                     trans.Commit();
34                     Console.WriteLine("事务成功了");
35                 }
36                 catch (Exception ex)
37                 {
38                     Console.WriteLine(ex.Message);
39                     trans.Rollback();
40 
41                 }
42                 finally
43                 {
44                     trans.Dispose();     //也可以把该事务写到using块中,让其自己托管,就不需要手动释放了
45                 }
46             }
47         }

2. 事务回滚

 1      private static void TestDbContextTransactionFailure()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 DbContextTransaction trans = null;
 6                 try
 7                 {
 8                     //开启事务
 9                     trans = db.Database.BeginTransaction();
10                     //增加
11                     string sql1 = @"insert into TestInfor  values(@id,@txt1,@txt2)";
12                     SqlParameter[] pars1 ={
13                                                  new SqlParameter("@id",Guid.NewGuid().ToString("N")),
14                                                  new SqlParameter("@txt1","txt11"),
15                                                  new SqlParameter("@txt2","txt22")
16                                             };
17                     db.Database.ExecuteSqlCommand(sql1, pars1);
18                     //删除
19                     string sql2 = @"delete from TestInfor where id=@id";
20                     SqlParameter[] pars2 ={
21                                                  new SqlParameter("@id","22")
22                                             };
23                     db.Database.ExecuteSqlCommand(sql2, pars2);
24                     //修改
25                     string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";
26                     SqlParameter[] pars3 ={
27                                                  new SqlParameter("@id","3"),
28                                                  new SqlParameter("@txt1","二狗子222")
29                                             };
30                     db.Database.ExecuteSqlCommand(sql3, pars3);
31 
32 
33                     //增加(制造错误)
34                     string sql4 = @"insert into TestInfor  values(@id,@txt1,@txt2)";
35                     SqlParameter[] pars4 ={
36                                                  new SqlParameter("@id",Guid.NewGuid().ToString("N")+"123"),
37                                                  new SqlParameter("@txt1","txt11"),
38                                                  new SqlParameter("@txt2","txt22")
39                                             };
40                     db.Database.ExecuteSqlCommand(sql4, pars4);
41 
42                     //提交事务
43                     trans.Commit();
44                     Console.WriteLine("事务成功了");
45                 }
46                 catch (Exception ex)
47                 {
48                     Console.WriteLine(ex.Message);
49                     trans.Rollback();
50 
51                 }
52                 finally
53                 {
54                     trans.Dispose();
55                 }
56             }
57         }

3. 自DbContextTransaction事务测试多个SaveChanges的情况统一回滚

 1   private static void DbContextTransSaveChanges()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 //自动脱管,不需要手动释放
 6                 using (DbContextTransaction trans = db.Database.BeginTransaction())
 7                 {
 8                     try
 9                     {
10                         TestInfor t1 = new TestInfor()
11                         {
12                             id = Guid.NewGuid().ToString("N"),
13                             txt1 = "111111111",
14                             txt2 = "222222222222"
15                         };
16                         db.Entry(t1).State = EntityState.Added;
17                         db.SaveChanges();
18 
19                         //人为制造失败情况
20                         TestInfor t2 = new TestInfor()
21                         {
22                             id = Guid.NewGuid().ToString("N") + "123",
23                             txt1 = "111111111",
24                             txt2 = "222222222222"
25                         };
26                         db.Entry(t2).State = EntityState.Added;
27                         db.SaveChanges();
28 
29                         trans.Commit();
30                     }
31                     catch (Exception)
32                     {
33                         trans.Rollback();
34                     }
35 
36                 }
37             }
38         }

四. TransactionScope事务

1. 该事务用来处理多个SaveChanges的事务(特殊情况的业务)或者多个DBContext(每个DBContext是一个实例,代表不同的数据库连接).

2. 核心代码:(一个Complete函数走天下,异常的话,自动回滚 ,也可以结合try-catch Transaction.Current.Rollback();实现回滚)

         需要引入程序集:using System.Transactions;

3. 适用场景:

  ①该种事务适用于多数据库连接的情况

  特别注意:如果使用该事务来处理多个数据库(多个DBContex)时,必须手动开启msdtc服务,这样才可以将多个DB的SaveChange给放到一个事务中,如果失败, 则多个数据库的数据统一回滚.

     开启msdtc服务的步骤: cmd命令→net start msdtc

    ②主键id自增的情况,同一个业务线中,需要拿到新增加的数据的主键id,进行操作。

      ③多线程带锁的情况,同一条业务线前半部分必须先SaveChanges,才能保证数据准确性(测试简单版本,实际的业务场景待补充!!!)

4. 主键自增被使用的情况的解决方案

 1     private static void Test3()
 2         {
 3             using (DbContext db = new CodeFirstModel())
 4             {
 5                 using (TransactionScope trans = new TransactionScope())
 6                 {
 7                     try
 8                     {
 9                         TestInfor2 t1 = new TestInfor2()
10                         {
11                             txt11 = Guid.NewGuid().ToString("N"),
12                             txt22 = Guid.NewGuid().ToString("N")
13                         };
14                         db.Set<TestInfor2>().Add(t1);
15                         //如果这里不写SaveChanges的话,t1.id永远为0
16                         db.SaveChanges();
17 
18 
19                         TestInfor2 t2 = new TestInfor2()
20                         {
21                             txt11 = (t1.id + 1).ToString(),
22                             txt22 = Guid.NewGuid().ToString("N")
23                         };
24                         db.Entry(t2).State = EntityState.Added;
25                         db.SaveChanges();
26 
27                         //事务统一提交(若失败则统一回滚)
28                         trans.Complete();
29                         Console.WriteLine("自增提交成功");
30                     }
31                     catch (Exception)
32                     {
33                         Transaction.Current.Rollback();
34                     }
35                 }    
36             }
37         }

5. 多个DBContext、多个数据库的情况。

 1  private static void TransactionScopeTwoDB()
 2         {
 3             using (TransactionScope trans = new TransactionScope())
 4             {
 5                 try
 6                 {
 7                     DbContext db1 = new CodeFirstModel();
 8                     DbContext db2 = new CodeFirstModel2();
 9                     //数据库1
10                     TestInfor t1 = new TestInfor()
11                     {
12                         id = Guid.NewGuid().ToString("N"),
13                         txt1 = "111111111",
14                         txt2 = "222222222222"
15                     };
16                     db1.Entry(t1).State = EntityState.Added;
17                     db1.SaveChanges();
18 
19                     //数据库2
20                     TestOne t2 = new TestOne()
21                     {
22                         id = Guid.NewGuid().ToString("N"),    //可以在此处手动制造个错误,来测试多数据库的回滚问题
23                         t1 = "222",
24                         t2 = "2222"
25                     };
26                     db2.Entry(t2).State = EntityState.Added;
27                     db2.SaveChanges();
28 
29                     trans.Complete();
30                     Console.WriteLine("多个数据库添加成功");
31                 }
32                 catch (Exception)
33                 {
34                     Transaction.Current.Rollback();
35                 }
36 
37             }
38         }

 

转载于:https://www.cnblogs.com/yaopengfei/p/7748221.html

<think>我们正在讨论C#中的TransactionScope类。根据引用[1][2],TransactionScope是一个用于简化事务管理的类,它可以确保一系列操作要么全部成功提交,要么全部回滚,从而保持数据的一致性。它支持分布式事务本地事务。 根据引用[3],TransactionScope使用TransactionScopeOption枚举来决定事务行为,默认值为Required,表示如果已有事务则加入,否则新建一个事务。此外,还可以通过TransactionOptions设置事务的超时隔离级别。 现在,我们将详细解释TransactionScope的使用方法,包括创建事务范围、事务选项、嵌套事务以及异常处理等。 ### TransactionScope基本使用 使用TransactionScope的基本步骤如下: 1. 创建`TransactionScope`对象。 2. 在`using`块内执行数据库操作(如多个数据库操作)。 3. 如果所有操作成功,调用`Complete()`方法提交事务。 4. 如果出现异常事务自动回滚。 示例代码: ```csharp using (TransactionScope scope = new TransactionScope()) { try { // 执行数据库操作1 // 执行数据库操作2 // 如果所有操作成功,则标记事务为完成 scope.Complete(); } catch (Exception ex) { // 事务自动回滚(因为未调用Complete) // 处理异常 } } ``` ### 事务选项 在创建`TransactionScope`时,可以传入`TransactionOptions`来设置事务的超时时间隔离级别。例如: ```csharp TransactionOptions options = new TransactionOptions(); options.IsolationLevel = IsolationLevel.ReadCommitted; // 设置隔离级别 options.Timeout = TimeSpan.FromSeconds(30); // 设置超时时间 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options)) { // ... 操作 scope.Complete(); } ``` ### 嵌套事务 `TransactionScope`支持嵌套事务。嵌套事务的行为由`TransactionScopeOption`控制: - `Required`:如果外部有事务,则加入外部事务;否则创建新事务。这是默认值。 - `RequiresNew`:无论外部是否有事务,都创建新事务。 - `Suppress`:不参与事务,即使外部有事务。 示例: ```csharp using (TransactionScope outerScope = new TransactionScope()) { // 外部事务 using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.RequiresNew)) { // 内部事务(独立于外部事务) innerScope.Complete(); } outerScope.Complete(); } ``` ### 分布式事务 当`TransactionScope`涉及多个不同的资源管理器(如两个不同的数据库)时,事务自动升级为分布式事务(由MSDTC协调)。这要求MSDTC服务在相关服务器上运行。 ### 注意事项 1.事务范围内,应避免长时间持有资源(如数据库连接),因为事务会锁定资源。 2. 分布式事务(MSDTC)可能带来性能开销,且需要配置网络权限。 3. 确保在操作成功后调用`Complete()`方法,且只调用一次。调用`Complete()`后,不能再进行任何操作。 ### 与依赖注入框架集成 根据引用[4],在使用依赖注入框架(如Autofac)时,需要注意`TransactionScope`的生命周期。通常,`TransactionScope`的生命周期应限定在某个作用域内(如一个HTTP请求)。例如,在Autofac中,可以使用`InstancePerLifetimeScope`来确保每个作用域内只有一个`TransactionScope`实例。 ### 异步支持 从.NET Framework 4.5.1开始,`TransactionScope`支持异步操作。在异步方法中,可以使用`TransactionScopeAsyncFlowOption.Enabled`来启用异步流支持: ```csharp using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { // 异步操作 await SomeAsyncMethod(); scope.Complete(); } ``` ### 异常处理 在事务范围内,如果发生异常事务回滚。如果异常被捕获,但未调用`Complete()`,事务也会回滚。因此,通常不需要在`catch`块中显式回滚。 ### 总结 `TransactionScope`提供了一种声明式的事务管理方式,简化了事务编程。通过合理设置事务选项注意嵌套行为,可以灵活控制事务边界。同时,注意分布式事务的配置性能影响。 §§相关问题§§ 1. TransactionScope在分布式事务中是如何工作的? 2. 如何配置MSDTC以支持分布式事务? 3. 在ASP.NET Core中如何使用TransactionScope? 4. TransactionScope数据库事务(如SqlTransaction)有什么区别? 5. 如何避免TransactionScope导致的死锁问题?</think>### C# TransactionScope 使用详解 `TransactionScope` 是 .NET 中简化事务管理的核心类,它提供**隐式事务管理**,确保代码块内所有操作要么全部成功提交,要么全部回滚。以下是详细使用指南: #### 一、基础用法 ```csharp using (var scope = new TransactionScope()) { // 1. 数据库操作1 (EF Core SaveChanges) dbContext.Orders.Add(order); await dbContext.SaveChangesAsync(); // 2. 数据库操作2 dbContext.Inventory.Update(stock); await dbContext.SaveChangesAsync(); // 3. 显式提交事务(必须调用!) scope.Complete(); // 提交事务 } // 未调用Complete()自动回滚 ``` **关键点**: 1. `using` 代码块定义事务边界 2. **必须调用** `scope.Complete()` 提交事务 3. 未调用 `Complete()` 或发生异常自动回滚 #### 二、核心配置选项 ```csharp var options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, // 隔离级别 Timeout = TimeSpan.FromSeconds(30) // 超时时间 }; using (var scope = new TransactionScope( TransactionScopeOption.Required, options, TransactionScopeAsyncFlowOption.Enabled) // 启用异步支持 { // 异步操作 await ProcessAsync(); scope.Complete(); } ``` **TransactionScopeOption 详解**: - `Required`(默认):加入现有事务或创建新事务 - `RequiresNew`:始终创建新事务 - `Suppress`:不参与事务(慎用) #### 三、嵌套事务控制 ```csharp using (var outerScope = new TransactionScope()) { // 父事务 using (var innerScope = new TransactionScope( TransactionScopeOption.Required, new TransactionOptions { Timeout = TimeSpan.FromMinutes(1) } )) { // 子事务(继承父事务设置) innerScope.Complete(); } outerScope.Complete(); } ``` 嵌套规则: - 所有嵌套块必须调用 `Complete()` 父事务才会提交 - 子事务超时设置覆盖父事务 #### 四、分布式事务处理 当涉及多个资源管理器(如SQL Server + Oracle)时,自动升级为**MSDTC分布式事务**: ```csharp using (var scope = new TransactionScope()) { sqlServerDb.ExecuteCommand("UPDATE A..."); oracleDb.ExecuteCommand("UPDATE B..."); scope.Complete(); // 触发两阶段提交(2PC) } ``` **必要条件**: 1. 启用 MSDTC 服务(Windows 服务) 2. 配置防火墙允许 MSDTC 通信 #### 五、异步操作支持 .NET 4.5.1+ 需显式启用异步流: ```csharp using (var scope = new TransactionScope( TransactionScopeAsyncFlowOption.Enabled)) { await Task.Run(() => { /* 异步操作 */ }); scope.Complete(); } ``` #### 六、最佳实践 1. **事务范围最小化**:避免长时间持有事务锁 2. **超时设置**:生产环境建议 10-30 秒 3. **异常处理**: ```csharp try { using (var scope = new TransactionScope(...)) { ... } } catch (TransactionAbortedException ex) { // 处理回滚 } catch (TransactionInDoubtException ex) { // 处理不确定状态 } ``` 4. **依赖注入集成**(如 Autofac): ```csharp builder.RegisterType<TransactionScope>() .InstancePerLifetimeScope(); // 作用域生命周期[^4] ``` #### 七、典型应用场景 1.数据库的原子操作(SQL Server + MySQL) 2. 消息队列与数据库一致性(如:数据库保存 + RabbitMQ 发送) 3. 文件操作与数据库更新事务 4. 微服务架构中的 Saga 模式实现基础 > ⚠️ **注意**:在云原生环境(如 Docker/Kubernetes)中,需额外配置 MSDTC 网络策略[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值