EF Core利用Transaction对数据进行回滚保护

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: What? 首先,说一下什么是EF Core中的Transaction Transaction允许以原子方式处理多个数据库操作,如果事务已提交,则所有操作都应用于数据库,如果事务回滚,则没有任何操作应用于数据库。

 

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应用程序

clip_image002[1]

选择Asp.NET Core Web应用程序

clip_image004[1]

.选择WebApi

搭建EF Core

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

clip_image006[1]

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程序包,

clip_image008[1]

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

clip_image010[1]

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

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

QQ截图20180327202848

修改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报管理器下的程序包管理控制台

clip_image014[1]

先后执行以下两条语句

Add-Migrition Init

Updata-Database

执行效果如图:

clip_image016[1]

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

clip_image018[1]

添加控制器(业务代码)

在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 == 0)
            {
                Wallet AUser = new Wallet()
                {
                    Name = "A",
                    Money = 100
                };
                Wallet BUser = new Wallet()
                {
                    Name = "B",
                    Money = 100
                };
                _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 -= 10;
            _bankContext.SaveChanges();
        }
        /// <summary>
        /// 从B的账户里面加上10元
        /// </summary>
        private void BAction()
        {
            var BUser = _bankContext.Wallets.Where(u => u.Name == "B").FirstOrDefault();
            BUser.Money += 10;
            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语句。

QQ截图20180327205604

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

clip_image020[1]

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

clip_image022[1]

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

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

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

clip_image024[1]

why?

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

 

Benefit?

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

 

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

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

 
相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
SQL Server 事务执行、回滚
SQL Server 事务执行、回滚
54 0
SQL server的事务锁--概念篇
在我看来,锁,就是一种手段,在现实生活中的意义就是锁住你不想让别人看到的或者不让别人进去的某个事物,具有一定的私密性。
Oracle中控制commit的三个参数 commit_write, commit_logging和 commit_wait
Oracle中控制commit的动作有三个参数 commit_write, commit_logging和 commit_wait,按重要性分别说明如下
311 0
Could not open Hibernate Session for transaction; nested exception is org.hibernate.TransactionExcep linux下mysql修改连接超时wait_timeout修改后就ok了
Could not open Hibernate Session for transaction; nested exception is org.hibernate.TransactionExcep linux下mysql修改连接超时wait_timeout修改后就ok了
194 1
MySQL:5.6 大事务show engine innodb status故障一例
MySQL:5.6 大事务show engine innodb status故障一例
211 0
MySQL:5.6 大事务show engine innodb status故障一例
【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only(下)
【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only(下)
【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only(下)
【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only(上)
【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only(上)
处理令人心烦的数据库事务日志 (SQL Server Transaction Log Files)
经常, 我们会被过快增长的数据库事务日志Transaction Log而困扰, 如果我们没有正确及时的处理, 可能会造成数据库交易无法进行, 服务器磁盘空间占光等问题. 在SQL Server的使用过程中, 我经常帮助用户和数据库的维护人员处理日志Transaction Log太大后造成的系统瘫痪的问题. 其实这个问题很容易避免. 今天我给大家分享下, 是什么造成了日志增长过大的问题. 和如何避免这种问题再次发生. 该文章的语句适用于SQL Server 2015 及其以后的版本 每一个数据库至少有两个文件: 一个是数据文件(Data file), 一个是事务日志文件(Transaction
821 0