第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges、DBContextTransaction、TransactionScope)
一. 什么是事务
我们通俗的理解事务就是一系列操作要么全部成功、要么全部失败(不可能存在部分成功,部分失败的情况)。
举一个事务在我们日常生活中的经典例子:两张银行卡(甲、乙),甲向乙转钱,整个过程需要执行两个操作,甲账户钱减少,乙账户钱增加,这是转账成功的情况;转账失败时候,二者均不执行,甲乙账户钱都不变。
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 try7 {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 try7 {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 try9 { 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 try8 {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 try6 {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 }
第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges、DBContextTransaction、TransactionScope)相关推荐
- 第十四节: EF的三种模式(四) 之 原生正宗的 CodeFirst模式的默认约定
一. 简介 1. 正宗的CodeFirst模式是不含有edmx模型,需要手动创建实体.创建EF上下文,然后生成通过代码来自动映射生成数据库. 2. 旨在:忘记SQL.忘记数据库. 3. 三类配置:On ...
- 第十节: EF的三种追踪实体状态变化方式(DBEntityEntry、ChangeTracker、Local)
一. 简介 我们在前面章节介绍EF基本增删改的时候,曾说过EF的SaveChanges()方法,会一次性的将所有的实体的状态变化统一提交到数据库,那么你是否想过EF的实体会有哪些状态变化呢?什么原因会 ...
- 第十一节: EF的三种模式(一) 之 DBFirst模式(SQLServer和MySQL两套方案)
一. 简介 EF连接数据库有三种模式,分别是DBFirst.ModelFirst.CodeFirst,分别适用于不同的开发场景. 该章节,将主要介绍EF的DBFirst连接SQLServer数据库和M ...
- 小白面试:EF Core的三种事务
有一次小白面试回来说,面试官问EF Core怎么实现,他当时懵逼了,之前写过但是长时间不用了,最近的事务还是用第三方封装的方法,早忘记原生的怎么写了.本文将替小白回答这个问题,而且有三种实现方法. 事 ...
- Java面试题之Oracle 支持哪三种事务隔离级别
Oracle 支持三种事务隔离级别: 1.读已提交:(默认) 2.串行化: 3.只读模式 转载于:https://www.cnblogs.com/hujinshui/p/10463883.html
- 联邦学习的基本概念、三种框架和应用场景
联邦学习的基本概念.三种框架和应用场景 前言 本文主要总结记录了联邦学习的基本概念.三种基本框架及其应用场景,下篇将从威胁模型和攻防现状入手介绍联邦学习的安全隐私现状- 一.提出背景 需求: 人工智能 ...
- 三种等待时间原理及场景
让我们来探讨探讨三种等待时间原理及场景 在使用web端自动化实施的过程中,常常会遇到页面还没加载完毕,脚本已经执行了,导致用例执行失败,为了确保页面加载完毕后再执行下一个脚本,我们通常采用设置等待的时 ...
- Spring的三种事务控制
Spring的三种方式的事务控制 1:基于编程式的事务控制 1.1:编程式事务控制相关对象 PlatformTransactionManager接口是spring的事务管理器,他里面提供了我们常用的操 ...
- Sql Server 三种事务模式
显式事务 显式事务(Explicit Transaction)是由设计人员明确定义事务的启动与结束的一种事务. 可使用 BEGIN TRANSACTION.COMMIT TRANSACTION.COM ...
最新文章
- 大端(Big Endian)与小端(Little Endian)详解
- linux 模式表达式,linux正则表达式和扩展表达式
- 实现点击按钮复制文本(Clipboard包)
- ASP.NET 安全认证(四)
- 【TensorFlow】TensorFlow从浅入深系列之十 -- 教你认识卷积神经网络的基本网路结构及其与全连接神经网络的差异
- @WebService作用
- 随想录(mac下c、c++的编译方法)
- hue访问mysql,【原创】大叔经验分享(50)hue访问mysql(librdbms)
- struts2在action中获取request、session、application,并传递数据
- BetaFlight深入传感设计之二:Mag传感模块
- 红外接收头图片_常见红外接收头、遥控接收头图片、引脚定义
- win7连接xp共享打印机方法
- 猪悟能淘宝商品下载专家v2.0 beta1.0版发布,欢迎测试!
- 关于打印时怎样不出现打印机选项而直接打印的解决方法
- iOS 网络请求劫持
- 逻辑备份数据导入原分区表遇到的那些事儿
- 孢子java_孢子框架-接口访问层、ESB、微服务API GateWay对比
- 判断数码管是共阳极还是共阴极
- 郭天祥单片机视频教程习题答案(一)
- Windows加密文件夹设置
热门文章
- jdbc执行sql 不支持“variant”数据类型。
- python之os、sys和random模块
- php: +1天, +3个月, strtotime(): +1 day, +3 month
- SQL Server 2012自动备份
- 使用的组件:ckeditor
- OpenStack Weekly Rank 2015.08.24
- ExecuteScalar
- Sublime Text 2 VS Vim
- unity3d 捕获系统日志,来处理一些问题
- 67、ulimit的使用