实验目示
观察EFCore中使用Transaction或不使用的情况下,数据的一致性情况。
实验环境
开发环境
- Windows 10
- VS2017
- Mssql 2014
组件
- Microsoft.EntityFrameworkCore 2.2.0
- Microsoft.EntityFrameworkCore.SqlServer 2.2.0
- System.Data.SqlClient 4.6.1
实验记录
实验数据库
建库
USE [master]
GO
/****** Object: Database [Basket] Script Date: 2019-08-13 11:01:45 ******/
CREATE DATABASE [Basket]
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'Basket', FILENAME = N'D:\Database\MSSQL12.MSSQLSERVER\MSSQL\DATA\Basket.mdf' , SIZE = 376832KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
LOG ON
( NAME = N'Basket_log', FILENAME = N'D:\Database\MSSQL12.MSSQLSERVER\MSSQL\DATA\Basket_log.ldf' , SIZE = 470144KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO
ALTER DATABASE [Basket] SET COMPATIBILITY_LEVEL = 120
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [Basket].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO
ALTER DATABASE [Basket] SET ANSI_NULL_DEFAULT OFF
GO
ALTER DATABASE [Basket] SET ANSI_NULLS OFF
GO
ALTER DATABASE [Basket] SET ANSI_PADDING OFF
GO
ALTER DATABASE [Basket] SET ANSI_WARNINGS OFF
GO
ALTER DATABASE [Basket] SET ARITHABORT OFF
GO
ALTER DATABASE [Basket] SET AUTO_CLOSE OFF
GO
ALTER DATABASE [Basket] SET AUTO_SHRINK OFF
GO
ALTER DATABASE [Basket] SET AUTO_UPDATE_STATISTICS ON
GO
ALTER DATABASE [Basket] SET CURSOR_CLOSE_ON_COMMIT OFF
GO
ALTER DATABASE [Basket] SET CURSOR_DEFAULT GLOBAL
GO
ALTER DATABASE [Basket] SET CONCAT_NULL_YIELDS_NULL OFF
GO
ALTER DATABASE [Basket] SET NUMERIC_ROUNDABORT OFF
GO
ALTER DATABASE [Basket] SET QUOTED_IDENTIFIER OFF
GO
ALTER DATABASE [Basket] SET RECURSIVE_TRIGGERS OFF
GO
ALTER DATABASE [Basket] SET DISABLE_BROKER
GO
ALTER DATABASE [Basket] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
GO
ALTER DATABASE [Basket] SET DATE_CORRELATION_OPTIMIZATION OFF
GO
ALTER DATABASE [Basket] SET TRUSTWORTHY OFF
GO
ALTER DATABASE [Basket] SET ALLOW_SNAPSHOT_ISOLATION OFF
GO
ALTER DATABASE [Basket] SET PARAMETERIZATION SIMPLE
GO
ALTER DATABASE [Basket] SET READ_COMMITTED_SNAPSHOT OFF
GO
ALTER DATABASE [Basket] SET HONOR_BROKER_PRIORITY OFF
GO
ALTER DATABASE [Basket] SET RECOVERY FULL
GO
ALTER DATABASE [Basket] SET MULTI_USER
GO
ALTER DATABASE [Basket] SET PAGE_VERIFY CHECKSUM
GO
ALTER DATABASE [Basket] SET DB_CHAINING OFF
GO
ALTER DATABASE [Basket] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF )
GO
ALTER DATABASE [Basket] SET TARGET_RECOVERY_TIME = 0 SECONDS
GO
ALTER DATABASE [Basket] SET DELAYED_DURABILITY = DISABLED
GO
ALTER DATABASE [Basket] SET READ_WRITE
GO
建表
USE [Basket]
GO
/****** Object: Table [dbo].[UserClasses] Script Date: 2019-08-13 11:01:37 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[UserClasses](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserId] [int] NULL,
[CaseId] [varchar](50) NULL,
[Name] [varchar](50) NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
/****** Object: Table [dbo].[Users] Script Date: 2019-08-13 11:01:37 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Users](
[Id] [int] IDENTITY(1,1) NOT NULL,
[CaseId] [varchar](50) NULL,
[Name] [varchar](50) NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
实验模型
public class AwpGroundDbContext : DbContext
{
public AwpGroundDbContext()
{
}
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<UserClass> UserClasses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=.;Database=Basket;Trusted_Connection=True;");
}
}
public class User
{
public int Id { get; set; }
public string CaseId { get; set; }
public string Name { get; set; }
}
public class UserClass
{
[Key]
public int Id { get; set; }
public int UserId { get; set; }
public string CaseId { get; set; }
public string Name { get; set; }
}
实验过程
1、不使用事务,对两个表的数据分别SaveChanges()
,并在中间抛出异常
using(var db = new AwpGroundDbContext())
{
var user = new User
{
Id = 0,
CaseId = Guid.NewGuid().ToString("N"),
Name = "user3"
};
db.Users.Add(user);
db.SaveChanges();
throw new Exception("在这里抛出了异常");
db.UserClasses.Add(
new UserClass
{
Id = 0,
UserId = user.Id,
CaseId = user.CaseId,
Name = "小一班"
});
db.SaveChanges();
}
数据表Users
成功保存了数据,UserClasses
无数据。
2、一次性SaveChanges()
,未发生异常的情况下,是否能够拿到UserId呢?
using(var db = new AwpGroundDbContext())
{
var user = new User
{
Id = 0,
CaseId = Guid.NewGuid().ToString("N"),
Name = "user3"
};
db.Users.Add(user);
db.UserClasses.Add(
new UserClass
{
Id = 0,
UserId = user.Id,
CaseId = user.CaseId,
Name = "小一班"
});
db.SaveChanges();
}
答案是,没有!所以,主从结构的保存时,不适用这种方式
3、使用事务,分别调用SaveChanges()
,能否拿到UserId
呢?
using (var db = new AwpGroundDbContext())
{
using (var tran = db.Database.BeginTransaction())
{
var user = new User
{
Id = 0,
CaseId = Guid.NewGuid().ToString("N"),
Name = "user3"
};
db.Users.Add(user);
db.SaveChanges();
db.UserClasses.Add(
new UserClass
{
Id = 0,
UserId = user.Id,
CaseId = user.CaseId,
Name = "小一班"
});
db.SaveChanges();
tran.Commit();
}
}
显然这样是可以的。
4、使用事务,中间添加异常,未调用Rollback(),是否会出现保存了部分数据的情况呢?
using (var db = new AwpGroundDbContext())
{
using (var tran = db.Database.BeginTransaction())
{
var user = new User
{
Id = 0,
CaseId = Guid.NewGuid().ToString("N"),
Name = "user5"
};
db.Users.Add(user);
db.SaveChanges();
throw new Exception("出错了");
db.UserClasses.Add(
new UserClass
{
Id = 0,
UserId = user.Id,
CaseId = user.CaseId,
Name = "小一班"
});
db.SaveChanges();
tran.Commit();
}
}
可以看到,User5并未保存。
结论
由于SaveChanges()单独创建了一个事务执行,所以当我们要保证多个表的数据一致性时,需要创建一个事务,并在事务中执行SaveChanges(),当其中出现错误时,事务未提交,不影响数据库,所以不需要手工Rollback。