一. 说明
EF版本的事务介绍详见:
第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges、DBContextTransaction、TransactionScope)。
本节主要介绍EF Core下的三种事务的用法和各自的使用场景,其中SaveChanges和DBContextTransaction事务与EF版本的基本一致,在该章节中补充一些新的使用场景和配置方式,TransactionScope环境事务与EF 版本的有着本质的区别,它目前不支持分布式数据库事务。
后面章节将继续介绍事务的基础概念、事务的隔离级别和带来的各种问题。
二. 默认事务(SaveChanges)
(1).默认情况下,如果数据库提供程序支持事务,单个 SaveChanges() 调用中的所有变更都会在一个事务中被提交。如果其中任何一个变更失败了, 那么事务就会回滚,没有任何变更会被应用到数据库。这意味着 SaveChanges() 能够确保要么成功保存,要么在发生错误时不对数据库做任何修改。
(2).关闭默认事务:context.Database.AutoTransactionsEnabled = false; 如:Test3()方法,第一条数据保存成功,第二条失败。
代码分享:
1 /// <summary> 2 /// 全部成功 3 /// </summary> 4 public static void Test1() 5 { 6 using (EFDB01Context db = new EFDB01Context()) 7 { 8 db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now }); 9 db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员2", addTime = DateTime.Now }); 10 db.SaveChanges(); 11 } 12 } 13 14 /// <summary> 15 /// 全部失败 16 /// </summary> 17 public static void Test2() 18 { 19 using (EFDB01Context db = new EFDB01Context()) 20 { 21 try 22 { 23 db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now }); 24 db.T_RoleInfor.Add(new T_RoleInfor() { id = 123, roleName = "管理员2", addTime = DateTime.Now }); 25 db.SaveChanges(); 26 } 27 catch (Exception) 28 { 29 Console.WriteLine("出错了,两条数据都没有执行成功"); 30 } 31 } 32 } 33 34 /// <summary> 35 /// 第一条成功,第二条失败 36 /// </summary> 37 public static void Test3() 38 { 39 using (EFDB01Context db = new EFDB01Context()) 40 { 41 try 42 { 43 //关闭SaveChanges的默认事务 44 db.Database.AutoTransactionsEnabled = false; 45 46 db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now }); 47 db.T_RoleInfor.Add(new T_RoleInfor() { id = 123, roleName = "管理员2", addTime = DateTime.Now }); 48 49 //db.T_UserInfor.Add(new T_UserInfor() { id = Guid.NewGuid().ToString("N"), userName = "管理员1", addTime = DateTime.Now }); 50 //db.T_UserInfor.Add(new T_UserInfor() { id = Guid.NewGuid().ToString("N")+"123", userName = "管理员2", addTime = DateTime.Now }); 51 52 db.SaveChanges(); 53 } 54 catch (Exception) 55 { 56 Console.WriteLine("出错了,第一条数据插入成功了"); 57 } 58 } 59 }
三. DbContextTransaction
1. 使用方式
BeginTransaction开启事务、Commit提交事务、Rollback回滚事务、Dispose销毁,如果用Using包裹的话,不再需要手动Rollback,走完Using会自动回滚。如果不用Using包裹事务,就需要在Catch中手动RollBack回滚,并且最好最后手动的Dispose一下。(如SameDbContext文件夹中的Test1和Test2方法)
2. 使用场景
A. 同一个上下文多个SaveChanges的方法(如:自增主键后续要用到,如Test2方法)、SaveChanges和EF调用SQL语句混用(如Test2方法)
1 /// <summary> 2 /// 三条添加语句共享同一个事务,最后使用 transaction.Commit() 统一提交,三条全部执行成功,则影响到数据库, 3 /// 如果任何一个命令失败,则在事务被回收(Dispose)时会自动回滚,对数据库无影响。 4 /// </summary> 5 public static void Test1() 6 { 7 using (EFDB01Context db = new EFDB01Context()) 8 { 9 using (var transaction = db.Database.BeginTransaction()) 10 { 11 try 12 { 13 db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now }); 14 db.SaveChanges(); 15 16 db.T_RoleInfor.Add(new T_RoleInfor() { id = 111, roleName = "管理员2", addTime = DateTime.Now }); //报错 17 db.SaveChanges(); 18 19 string sql1 = @"insert into T_RoleInfor (roleName,roleDescription,addTime) values (@roleName,@roleDescription,@addTime)"; 20 SqlParameter[] pars1 ={ 21 new SqlParameter("@roleName","管理员3"), 22 new SqlParameter("@roleDescription","txt11"), 23 new SqlParameter("@addTime",DateTime.Now) 24 }; 25 db.Database.ExecuteSqlCommand(sql1, pars1); 26 transaction.Commit(); 27 28 Console.WriteLine("成功了"); 29 } 30 catch (Exception) 31 { 32 Console.WriteLine("失败了"); 33 } 34 } 35 } 36 } 37 38 /// <summary> 39 /// 如果不用Using包裹事务,就需要在Catch中手动RollBack回滚 40 /// </summary> 41 public static void Test2() 42 { 43 using (EFDB01Context db = new EFDB01Context()) 44 { 45 var transaction = db.Database.BeginTransaction(); 46 try 47 { 48 var d1 = new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now }; 49 db.T_RoleInfor.Add(d1); 50 db.SaveChanges(); 51 52 db.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员2"+d1.id, addTime = DateTime.Now }); 53 db.SaveChanges(); 54 55 string sql1 = @"insert into T_RoleInfor (roleName,roleDescription,addTime) values (@roleName,@roleDescription,@addTime)"; 56 SqlParameter[] pars1 ={ 57 new SqlParameter("@roleName","管理员3"), 58 new SqlParameter("@roleDescription","txt11"), 59 new SqlParameter("@addTime",DateTime.Now) 60 }; 61 db.Database.ExecuteSqlCommand(sql1, pars1); 62 transaction.Commit(); 63 64 Console.WriteLine("成功了"); 65 66 } 67 catch (Exception) 68 { 69 transaction.Rollback(); 70 Console.WriteLine("失败了"); 71 } 72 finally 73 { 74 transaction.Dispose(); 75 } 76 77 } 78 }
B. 同一个数据库多个上下文但“同一个连接”的事务。其中一个上下文开启事务,另外上下文通过UseTransaction方法来实现共享事务。
情况①:
EFDB01Context直接在OnConfiguring中写死连接字符串,多次new上下文,如Test1方法,则是多个连接,不能共享事务。
1 /// <summary> 2 /// 情况一:在OnConfiguring中书写连接字符串,创建两个上下文,相当于两个连接,两个连接之间不能通过使用UseTransaction,建立事务连接。 3 /// 会报下面的错。 4 /// The specified transaction is not associated with the current connection. Only transactions associated with the current connection may be used. 5 /// </summary> 6 public static void Test1() 7 { 8 using (EFDB01Context context1 = new EFDB01Context()) 9 { 10 using (var transaction = context1.Database.BeginTransaction()) 11 { 12 try 13 { 14 context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now }); 15 context1.SaveChanges(); 16 17 using (EFDB01Context context2 = new EFDB01Context()) 18 { 19 context2.Database.UseTransaction(transaction.GetDbTransaction()); 20 21 context1.T_RoleInfor.Add(new T_RoleInfor() { roleName = "管理员1", addTime = DateTime.Now }); 22 context1.SaveChanges(); 23 } 24 25 //统一提交 26 transaction.Commit(); 27 Console.WriteLine("成功了"); 28 } 29 catch (Exception ex) 30 { 31 Console.WriteLine(ex.Message); 32 } 33 } 34 } 35 36 37 }