一. 什么是事务

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

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

1. SQLServer中的事务

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

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

2. EF中的事务

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

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

二. SaveChanges

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

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

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

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

1. 事务成功

      private static void TestSaveChanges()
{
using (DbContext db = new CodeFirstModel())
{
//增加
TestInfor t1 = new TestInfor()
{
id = Guid.NewGuid().ToString("N"),
txt1 = "txt1",
txt2 = "txt2"
};
db.Set<TestInfor>().Add(t1);
//删除
TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "").FirstOrDefault();
if (t2 != null)
{
db.Set<TestInfor>().Remove(t2);
}
//修改
TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "").FirstOrDefault();
t3.txt2 = "我是李马茹23"; //SaveChanges事务提交
int n = db.SaveChanges();
Console.WriteLine("数据作用条数:" + n);
}
}

2. 事务回滚

       private static void TestSaveChangesFailure()
{
using (DbContext db = new CodeFirstModel())
{
//增加
TestInfor t1 = new TestInfor()
{
id = Guid.NewGuid().ToString("N"),
txt1 = "txt1",
txt2 = "txt2"
};
db.Set<TestInfor>().Add(t1);
//删除
TestInfor t2 = db.Set<TestInfor>().Where(u => u.id == "").FirstOrDefault();
if (t2 != null)
{
db.Set<TestInfor>().Remove(t2);
}
//修改
TestInfor t3 = db.Set<TestInfor>().Where(u => u.id == "").FirstOrDefault();
t3.txt2 = "我是李马茹23";
//增加(制造错误)
TestInfor t4 = new TestInfor()
{
id = Guid.NewGuid().ToString("N") + "",
txt1 = "txt1",
txt2 = "txt2"
};
db.Set<TestInfor>().Add(t4); //SaveChanges事务提交
int n = db.SaveChanges();
Console.WriteLine("数据作用条数:" + n);
}
}

三. DbContextTransaction事务

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

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

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

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

1. 事务成功

      private static void TestDbContextTransaction()
{
using (DbContext db = new CodeFirstModel())
{
DbContextTransaction trans = null;
try
{
//开启事务
trans = db.Database.BeginTransaction();
//增加
string sql1 = @"insert into TestInfor values(@id,@txt1,@txt2)";
SqlParameter[] pars1 ={
new SqlParameter("@id",Guid.NewGuid().ToString("N")),
new SqlParameter("@txt1","txt11"),
new SqlParameter("@txt2","txt22")
};
db.Database.ExecuteSqlCommand(sql1, pars1);
//删除
string sql2 = @"delete from TestInfor where id=@id";
SqlParameter[] pars2 ={
new SqlParameter("@id","")
};
db.Database.ExecuteSqlCommand(sql2, pars2);
//修改
string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";
SqlParameter[] pars3 ={
new SqlParameter("@id",""),
new SqlParameter("@txt1","二狗子")
};
db.Database.ExecuteSqlCommand(sql3, pars3); //提交事务
trans.Commit();
Console.WriteLine("事务成功了");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
trans.Rollback(); }
finally
{
trans.Dispose(); //也可以把该事务写到using块中,让其自己托管,就不需要手动释放了
}
}
}

2. 事务回滚

      private static void TestDbContextTransactionFailure()
{
using (DbContext db = new CodeFirstModel())
{
DbContextTransaction trans = null;
try
{
//开启事务
trans = db.Database.BeginTransaction();
//增加
string sql1 = @"insert into TestInfor values(@id,@txt1,@txt2)";
SqlParameter[] pars1 ={
new SqlParameter("@id",Guid.NewGuid().ToString("N")),
new SqlParameter("@txt1","txt11"),
new SqlParameter("@txt2","txt22")
};
db.Database.ExecuteSqlCommand(sql1, pars1);
//删除
string sql2 = @"delete from TestInfor where id=@id";
SqlParameter[] pars2 ={
new SqlParameter("@id","")
};
db.Database.ExecuteSqlCommand(sql2, pars2);
//修改
string sql3 = @"update TestInfor set txt1=@txt1 where id=@id";
SqlParameter[] pars3 ={
new SqlParameter("@id",""),
new SqlParameter("@txt1","二狗子222")
};
db.Database.ExecuteSqlCommand(sql3, pars3); //增加(制造错误)
string sql4 = @"insert into TestInfor values(@id,@txt1,@txt2)";
SqlParameter[] pars4 ={
new SqlParameter("@id",Guid.NewGuid().ToString("N")+""),
new SqlParameter("@txt1","txt11"),
new SqlParameter("@txt2","txt22")
};
db.Database.ExecuteSqlCommand(sql4, pars4); //提交事务
trans.Commit();
Console.WriteLine("事务成功了");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
trans.Rollback(); }
finally
{
trans.Dispose();
}
}
}

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

   private static void DbContextTransSaveChanges()
{
using (DbContext db = new CodeFirstModel())
{
//自动脱管,不需要手动释放
using (DbContextTransaction trans = db.Database.BeginTransaction())
{
try
{
TestInfor t1 = new TestInfor()
{
id = Guid.NewGuid().ToString("N"),
txt1 = "",
txt2 = ""
};
db.Entry(t1).State = EntityState.Added;
db.SaveChanges(); //人为制造失败情况
TestInfor t2 = new TestInfor()
{
id = Guid.NewGuid().ToString("N") + "",
txt1 = "",
txt2 = ""
};
db.Entry(t2).State = EntityState.Added;
db.SaveChanges(); trans.Commit();
}
catch (Exception)
{
trans.Rollback();
} }
}
}

四. 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. 主键自增被使用的情况的解决方案

     private static void Test3()
{
using (DbContext db = new CodeFirstModel())
{
using (TransactionScope trans = new TransactionScope())
{
try
{
TestInfor2 t1 = new TestInfor2()
{
txt11 = Guid.NewGuid().ToString("N"),
txt22 = Guid.NewGuid().ToString("N")
};
db.Set<TestInfor2>().Add(t1);
//如果这里不写SaveChanges的话,t1.id永远为0
db.SaveChanges(); TestInfor2 t2 = new TestInfor2()
{
txt11 = (t1.id + ).ToString(),
txt22 = Guid.NewGuid().ToString("N")
};
db.Entry(t2).State = EntityState.Added;
db.SaveChanges(); //事务统一提交(若失败则统一回滚)
trans.Complete();
Console.WriteLine("自增提交成功");
}
catch (Exception)
{
Transaction.Current.Rollback();
}
}
}
}

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

  private static void TransactionScopeTwoDB()
{
using (TransactionScope trans = new TransactionScope())
{
try
{
DbContext db1 = new CodeFirstModel();
DbContext db2 = new CodeFirstModel2();
//数据库1
TestInfor t1 = new TestInfor()
{
id = Guid.NewGuid().ToString("N"),
txt1 = "",
txt2 = ""
};
db1.Entry(t1).State = EntityState.Added;
db1.SaveChanges(); //数据库2
TestOne t2 = new TestOne()
{
id = Guid.NewGuid().ToString("N"), //可以在此处手动制造个错误,来测试多数据库的回滚问题
t1 = "",
t2 = ""
};
db2.Entry(t2).State = EntityState.Added;
db2.SaveChanges(); trans.Complete();
Console.WriteLine("多个数据库添加成功");
}
catch (Exception)
{
Transaction.Current.Rollback();
} }
}

第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges、DBContextTransaction、TransactionScope)的更多相关文章

  1. ASP.NET MVC深入浅出(被替换) 第一节: 结合EF的本地缓存属性来介绍【EF增删改操作】的几种形式 第三节: EF调用普通SQL语句的两类封装(ExecuteSqlCommand和SqlQuery ) 第四节: EF调用存储过程的通用写法和DBFirst模式子类调用的特有写法 第六节: EF高级属性(二) 之延迟加载、立即加载、显示加载(含导航属性) 第十节: EF的三种追踪

    ASP.NET MVC深入浅出(被替换)   一. 谈情怀-ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态 ...

  2. 第十节: EF的三种追踪实体状态变化方式(DBEntityEntry、ChangeTracker、Local)

    一. 简介 我们在前面章节介绍EF基本增删改的时候,曾说过EF的SaveChanges()方法,会一次性的将所有的实体的状态变化统一提交到数据库,那么你是否想过EF的实体会有哪些状态变化呢?什么原因会 ...

  3. 第十四节: EF的三种模式(四) 之 原生正宗的 CodeFirst模式的默认约定

    一. 简介 1. 正宗的CodeFirst模式是不含有edmx模型,需要手动创建实体.创建EF上下文,然后生成通过代码来自动映射生成数据库. 2. 旨在:忘记SQL.忘记数据库. 3. 三类配置:On ...

  4. 第十一节: EF的三种模式(一) 之 DBFirst模式(SQLServer和MySQL两套方案)

    一. 简介 EF连接数据库有三种模式,分别是DBFirst.ModelFirst.CodeFirst,分别适用于不同的开发场景. 该章节,将主要介绍EF的DBFirst连接SQLServer数据库和M ...

  5. Java面试题之Oracle 支持哪三种事务隔离级别

    Oracle 支持三种事务隔离级别: 1.读已提交:(默认) 2.串行化: 3.只读模式

  6. Spring事务Transactional和动态代理(三)-事务失效的场景

    系列文章索引: Spring事务Transactional和动态代理(一)-JDK代理实现 Spring事务Transactional和动态代理(二)-cglib动态代理 Spring事务Transa ...

  7. Collection集合重难点梳理,增强for注意事项和三种遍历的应用场景,栈和队列特点,数组和链表特点,ArrayList源码解析, LinkedList-源码解析

    重难点梳理 使用到的新单词: 1.collection[kəˈlekʃn] 聚集 2.empty[ˈempti] 空的 3.clear[klɪə(r)] 清除 4.iterator 迭代器 学习目标: ...

  8. Entity Framework 5.0系列之EF概览-三种编程方式

    概述 在开发面向数据的软件时我们常常为了解决业务问题实体.关系和逻辑构建模型而费尽心机,ORM的产生为我们提供了一种优雅的解决方案.ADO.NET Entity Framework是.NET开发中一种 ...

  9. EF的三种模式

    1.DateBase First(数据库优先) 2.Model First(模型优先) 3.Code First(代码优先) 当然,如果把Code First模式的两种具体方式独立出来,那就是四种了. ...

随机推荐

  1. #018 C语言刷题 素数问题

    今天做题学会了一个求素数的方法 总分 13 孪生素数 相差为2的两个素数称为孪生素数.例如,3与5,41与43等都是孪生素数.设计程序求出指定区间上的所有孪生素数对.区间上限和下限由键盘获取. 程序运 ...

  2. JavaScript Uncaught TypeError: Cannot read property 'value' of null

    用 JavaScript 操作 DOM 时出现如下错误: Uncaught TypeError: Cannot set property 'value' of null Uncaught TypeEr ...

  3. golang json 读写配置文件

    package main import ( "encoding/json" "fmt" "os" ) type configuration ...

  4. Django-CRM项目学习(七)-权限组件的设置以及权限组件的应用

    开始今日份整理 1.利用自定制标签,增加展示权限,权限分级设定 1.1 在权限组件中创建自定义标签 使用自定义标签的目的,使各个数据进行分离 1.2 导入自定义标签包 自定义标签复习(自定义标签有三种 ...

  5. rtsp 流媒体服务器,播放器

    https://github.com/EasyDSS/EasyPlayer-RTSP-Android EasyPlayer EasyPlayer RTSP Android 播放器是由紫鲸团队开发和维护 ...

  6. java获取真实的IP地址工具类

    在实际项目中,有调用微信支付完成支付功能,在微信支付的请求参数中需要传递一个本机的ip地址,java代码运行环境目前为windows10以及centos7. 以下为获取ip地址工具类: package ...

  7. L2-2 重排链表 (25 分)

    给定一个单链表 L​1​​→L​2​​→⋯→L​n−1​​→L​n​​,请编写程序将链表重新排列为 L​n​​→L​1​​→L​n−1​​→L​2​​→⋯.例如:给定L为1→2→3→4→5→6,则输出 ...

  8. AWT的应用

    AWT(Abstract Window TookIT)是java用于创建用户界面和绘制图形的所有类 AWT组件和容器 AWT包含了很多类和接口,用于java Application的GUI(图形用户界 ...

  9. 编写python程序和运行.py文件的方法步骤

    前提:已安装好 Subliume Test 3 且已经添加好python编译系统,已安装好python3.7 一.新建一个文本文档,将后缀名改为.py 二.使用 Subliume Test 3 打开该 ...

  10. Linux中什么是动态网站环境及如何部署

    当谈论起网站时,我们可能听说过静态和动态这两个词,但却不知道它们的含义,或者从字面意思了解一些却不知道它们的区别. 这一切可以追溯到网站和网络应用程序,Web应用程序是一个网站,但很多网站不是Web应 ...