• EF Core利用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

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

     
  • 相关阅读:
    学习进度(6)
    学习进度(5)
    学习进度(4)
    学习进度(第十周)
    学习进度(第九周)
    关于返回一个整数数组中最大子数组的和的问题(续03)
    学习进度(第八周)
    代码大全阅读笔记03
    NABCD分析——生活日历
    学习进度(第七周)
  • 原文地址:https://www.cnblogs.com/CoderAyu/p/8660592.html
Copyright © 2020-2023  润新知