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

如果您覺得有幫助,請點擊推薦,謝謝。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM