What?

首先,说一下什么是EF Core中的Transaction

Transaction允许以原子方式处理多个数据库操作,如果事务已提交,则所有操作都应用于数据库,如果事务回滚,则没有任何操作应用于数据库。

所谓原子方式 是指对数据库的每一个操作是对立开来的,但是多个操作能合成一个整体(个人理解)。

当操作到某一步失败了,那么会触发事物的回滚,把前面成功的操作也进行撤销,为什么这一操作这么重要呢?我举个例子你就知道了

就那拿一行转账这件事情来说。正常的A给B转账X元有两步:

1. 从A的账户余额中减去X元。

2. 往B的银行账户中添加X元。

假如,第一步执行完了,第二部因为某种原因执行失败了,那么,是不是A的账户平白无故地少了X元而B并没有多X元呢?显然这种事情是不能发生的,正确的做法是,把第一步撤销,即把A账户减去的X元加上。

然而在在.Net中,如果你使用EF Core来操作数据库,这些都不用我们手动完成了,EF Core的事物完全可以帮我们完成这样的操作。

How?

下面我们利用一个asp.net core webapi的例子来讲解EF Core中这种Transaction的用法。

新建一个webapi应用程序

选择Asp.NET Core Web应用程序

.选择WebApi

搭建EF Core

创建Model文件夹和BankContext数据库上下文,Walet钱包实体,如图:

Wallet的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace EFCoreRollback.Models
{
public class Wallet
{
public int Id { get; set; }
public string Name { get; set; }
public double Money { get; set; }
}
}

BankContext的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; namespace EFCoreRollback.Models
{
public class BankContext:DbContext
{
public BankContext(DbContextOptions<BankContext> options) : base(options)
{ } public DbSet<Wallet> Wallets { get; set; }
}
}

因为我是用Mysql数据库进行数据存储的,所以需要添加Mysql的EF Core引用,选中依赖项,右键菜单 选择管理Nuget程序包,

安装下列引用项目(Pomelo.EntityFrameworkCore.MySql):

在appsettings.json中加入数据库连接字符串,如下:

"ConnectionStrings": { "Connection": "Data Source=127.0.0.1;Database=bank;User ID=root;Password=123456;" }

修改statup.cs,进行BankContext的依赖注入,主要修改了灰色部分,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EFCoreRollback.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; namespace EFCoreRollback
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{

var connectString = Configuration.GetConnectionString("Connection"); services.AddDbContext<BankContext>(options =>

            {
options.UseMySql(connectString);
options.UseLoggerFactory(

new LoggerFactory().AddConsole()); //加入该句会把EF Core执行过程中的Sql语句在控制台输出

            });
services.AddMvc();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseMvc();
}
}
}

创建数据库和表

打开NuGet报管理器下的程序包管理控制台

先后执行以下两条语句

Add-Migrition Init

Updata-Database

执行效果如图:

执行成功后,Mysql数据库中多了Bank数据库和walets表,如图:

添加控制器(业务代码)

在Controllers下新建一个BankController.cs,完整代码如下(核心部分为灰色背景):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using EFCoreRollback.Models;
using Microsoft.AspNetCore.Mvc; namespace EFCoreRollback.Controllers
{
public class BankController : Controller
{
private readonly BankContext _bankContext;
public BankController(BankContext context)
{
_bankContext = context;
}
/// <summary>
/// 数据初始化
/// </summary>
[HttpGet]
[Route("bangk/InitData")]
public string InitData()
{
if (_bankContext.Wallets.ToList().Count == )
{
Wallet AUser = new Wallet()
{
Name = "A",
Money =
};
Wallet BUser = new Wallet()
{
Name = "B",
Money =
};
_bankContext.Wallets.Add(AUser);
_bankContext.Wallets.Add(BUser);
_bankContext.SaveChanges();
}
return "Success";
}
/// <summary>
/// 进行转账
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("bank/TransferAccounts")]
public string TransferAccounts()
{

using (var transaction = _bankContext.Database.BeginTransaction()) { try

                {
AAction();
BAction();

//如果未执行到Commit()就执行失败遇到异常了,EF Core会自动进行数据回滚(前提是使用Using) transaction.Commit(); } catch (Exception ex) { // TODO: Handle failure return ex.Message; } } return "success"

;
}
/// <summary>
/// 从A的账户里面减掉10元
/// </summary>
private void AAction()
{
var AUser = _bankContext.Wallets.Where(u => u.Name == "A").FirstOrDefault();
AUser.Money -= ;
_bankContext.SaveChanges();
}
/// <summary>
/// 从B的账户里面加上10元
/// </summary>
private void BAction()
{
var BUser = _bankContext.Wallets.Where(u => u.Name == "B").FirstOrDefault();
BUser.Money += ;
throw new Exception("B的数据在保存前出现异常了"); //使用该方法模拟出现数据保存异常
_bankContext.SaveChanges();
} /// <summary>
/// 展示钱包账户
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("bank/Show")]
public List<Wallet> ShowWallets()
{
return _bankContext.Wallets.ToList();
} }
}

通过InitData方法,我们把数据初始化,往数据库中插入A、B用户,他们钱包的初始金额都为100元。

通过TransferAccounts方法,我们执行转账操作,通过using引入了EF Core的Transaction,如果未执行到Commit()就执行失败遇到异常了,EF Core会自动进行数据回滚(前提是使用Using)。

在执行AAction后,执行BAction,其中BAction在数据保存前,设置了一个异常。

执行接口(调用业务)

首先,其启动方式从IIS切换到WebAPi程序本身,为的是在控制台中看到输出的SQL语句。

程序成功启动后,我们调用数据初始化接口,效果如图:

有了数据后,我们调用转账接口进行转账操作,如图:

进行转账操作,在A的账户成功减掉10元后,在B的账户加上10元保存时,由于我们设置了异常,程序跳出了。

如果按照我们正常的思维方式,因为B在保存数据前异常了,所以最终结果因该是:A的账户少了10元,而B的账户金额未变。事实是不是这样呢?

我们执行Show接口,展示A和B用户的钱包金额情况,可以看到,A和B的钱包金额都是100,

why?

为什么A的账户明明执行了减去10元的操作,而最后没有生效呢?原来是在执行transaction.Commit()之前,程序遇到异常了,它会自动调用transaction.Rollback()进行数据回滚,撤销A的减去10元这一操作。

Benefit?

使用EF Core的Transaction要么所有操作全部成功,要么一个操作都不执行,可以保护数据安全。

该项目的完整代码:https://github.com/liuzhenyulive/EFCoreTransaction

如果您觉得有帮助,请点击推荐,谢谢。

 

EF Core利用Transaction对数据进行回滚保护的更多相关文章

  1. DataPipeline的增量数据支持回滚功能

    DataPipeline的增量数据支持回滚功能 第一步:数据任务有增量数据时,回滚按钮激活,允许用户使用该功能进行数据回滚. 第二步:点击回滚按钮,允许用户选择回滚时间或者回滚位置进行数据回滚.选择按 ...

  2. mybatis插值,数据提交事务回滚数据库值为空

    mybatis插值,数据提交事务回滚数据库值为空 通过sql日志查看sql为:INSERT INTO `quanxian`.`user` ( phone, email, password, times ...

  3. Dapper, Ef core, Freesql 插入大量数据性能比较(一)

    需求:导入9999行数据时Dapper, Ef core, Freesql 谁的性能更优,是如何执行的,级联增加谁性能更佳. 确认方法:sql server 的 sys.dm_exec_query_s ...

  4. EF Core利用Scaffold从根据数据库生成代码

    在EF6 之前的时代,如果需要从数据库中生成代码,是可以直接在界面上操作的,而到了EF Core的时代,操作方式又有更简便的方式了,我们只需要记住以下这条指令. Scaffold-DbContext ...

  5. .net core利用MySqlBulkLoader大数据批量导入MySQL

    最近用core写了一个数据迁移小工具,从SQLServer读取数据,加工后导入MySQL,由于数据量太过庞大,数据表都过百万,常用的dapper已经无法满足.三大数据库都有自己的大数据批量导入数据的方 ...

  6. C#代码启用事务锁Transaction进行一系列提交回滚操作

    一.前言 因为很多人一般进行一系列相关数据库操作都是在存储过程里面,而且在存储过程用锁的写法也是很简单的,在这篇文章主要介绍一下C#后台代码用锁进行一系列事务操作,我建立一个简单的winform程序, ...

  7. transaction 数据库事务 roolback 回滚

    事务是恢复和并发控制的基本单位 https://baike.baidu.com/item/数据库事务/9744607 事务有三种模型: 1.隐式事务是指每一条数据操作语句都自动地成为一个事务,事务的开 ...

  8. Dapper, Ef core, Freesql 插入大量数据性能比较(二)

    在上一篇文章中,我们比较出单表插入9999行数据,Dapper > EfCore > Freesql.在本文中,我们来看看级联插入 构建9999行数据 List<Entity> ...

  9. 【asp.net core 系列】8 实战之 利用 EF Core 完成数据操作层的实现

    0. 前言 通过前两篇,我们创建了一个项目,并规定了一个基本的数据层访问接口.这一篇,我们将以EF Core为例演示一下数据层访问接口如何实现,以及实现中需要注意的地方. 1. 添加EF Core 先 ...

随机推荐

  1. 修改android 开机画面

    对于使用安卓手机的人来说,能够自由定制手机的各种界面是每个用户之所以喜欢安卓系统的最根本的缘由,比如手机的开机界面中的bootanimation.zip文件.本文就如何修改开机界面,做一个简单的流程介 ...

  2. sudo:无法解析主机 解决方案

    你如果电脑中没有vim,用gedit也可以. 打开文件以后,将其中的 127.0.1.1  xxxxx xxx 替换成你电脑的自己的名字即可.

  3. TI AM335X处理器介绍

    AM335X是美国TI(德州仪器)公司基于 ARM Cortex-A8内核的AM335X微处理器,在图像.图形处理.外设方面进行了增强,并全面支持诸如 EtherCAT 和 PROFIBUS等工业接口 ...

  4. 利用ICSharpCode.SharpZipLib进行压缩

    #ZipLib is a Zip, GZip, Tar and BZip2 library written entirely in C# for the .NET platform. It is im ...

  5. jquery Dialog弹框插件

    function Dialog(options) { var defaults = { // 默认值. title: '', // 标题文本,若不想显示title请通过CSS设置其display为no ...

  6. Exynos4412从SD卡启动的简单网络文件系统制作

    Exynos4412从SD卡启动的简单网络文件系统制作 1. 简介 嵌入式系统能够在开发板上正常运行,需要先进行系统配置,一个完整的嵌入式系统应该包含的几个部分::uboot,kernel,rootf ...

  7. 自定义JS乘法运算误差解决!

    在实际开发中遇到这样一个乘法公式:数量*单价=总价 像这样的浮点数列子:200*8.2,JS算出的结果是: 像这种浮点数的乘法计算就会有误差,我们需要得到准确的值应该是:1640,与我们后台C#计算结 ...

  8. Python编程核心内容之二——切片、迭代和列表生成式

    Python版本:3.6.2  操作系统:Windows  作者:SmallWZQ 最近太忙啦.很多事情需要自己处理,感觉时间不够用啊~~~~今后,博客更新时间可能会慢下来,哈哈,正所谓"人 ...

  9. 【BZOJ1415】【NOI2005】聪聪和可可(动态规划,数学期望)

    [BZOJ1415][NOI2005]聪聪和可可(动态规划,数学期望) 题面 BZOJ 题解 先预处理出当可可在某个点,聪聪在某个点时 聪聪会往哪里走 然后记忆化搜索一下就好了 #include< ...

  10. (jzoj snow的追寻)线段树维护树的直径

    jzoj snow的追寻 DFS序上搞 合并暴力和,记录最长链和当前最远点,距离跑LCA # include <stdio.h> # include <stdlib.h> # ...