首先,说一下什么是EF Core中的Transaction
Transaction允许以原子方式处理多个数据库操作,如果事务已提交,则所有操作都应用于数据库,如果事务回滚,则没有任何操作应用于数据库。
所谓原子方式 是指对数据库的每一个操作是对立开来的,但是多个操作能合成一个整体(个人理解)。
当操作到某一步失败了,那么会触发事物的回滚,把前面成功的操作也进行撤销,为什么这一操作这么重要呢?我举个例子你就知道了
就那拿一行转账这件事情来说。正常的A给B转账X元有两步:
1. 从A的账户余额中减去X元。
2. 往B的银行账户中添加X元。
假如,第一步执行完了,第二部因为某种原因执行失败了,那么,是不是A的账户平白无故地少了X元而B并没有多X元呢?显然这种事情是不能发生的,正确的做法是,把第一步撤销,即把A账户减去的X元加上。
然而在在.Net中,如果你使用EF Core来操作数据库,这些都不用我们手动完成了,EF Core的事物完全可以帮我们完成这样的操作。
下面我们利用一个asp.net core webapi的例子来讲解EF Core中这种Transaction的用法。
选择Asp.NET Core Web应用程序
.选择WebApi
创建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)
{
打开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 == 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()
{
通过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,
为什么A的账户明明执行了减去10元的操作,而最后没有生效呢?原来是在执行transaction.Commit()之前,程序遇到异常了,它会自动调用transaction.Rollback()进行数据回滚,撤销A的减去10元这一操作。
使用EF Core的Transaction要么所有操作全部成功,要么一个操作都不执行,可以保护数据安全。