大家好,又见面了,我是你们的朋友全栈君。
TransactionScope
TransactionScope事务处理经常用到,老是用了又忘,做点记录。
TransactionScope的定义跟使用介绍。
TransactionScopeOption
TransactionScopeOption枚举型用来决定一个TransactionScope是用已有的事务,还是定义TransactionScope的新做一个事务,还是完全不用事务。默认是Required,Required表示如果已有事务,就加入该事务,否则新建一个事务。
TransactionOptions
TransactionOptions 结构体用来设置TransactionScope所用到事务的隔离级别与超时时间。
隔离级别参考这篇博文。(乐观锁)
实践
做两张表
USE [Learning]
GO
CREATE TABLE [dbo].[Foo](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Value] [nvarchar](50) NULL,
CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Bar](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Value] [nvarchar](50) NULL,
CONSTRAINT [PK_Bar] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
准备测试类
用了EntityFrameWorkCore的库,两个DbContext连同一个数据库。(本地测试的时候,连两个不同的数据库报错:EntityFrameWorkCore不支持分布式事务。。)
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Transactions;
namespace LocalAppTest
{
public class FooDbContext : DbContext
{
public FooDbContext(DbContextOptions<FooDbContext> options) : base(options) {
}
public DbSet<Foo> Foo {
get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
[Table("Foo")]
public class Foo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id {
get; set; }
[StringLength(50)]
public string Value {
get; set; }
}
public class BarDbContext : DbContext
{
public BarDbContext(DbContextOptions<BarDbContext> options) : base(options) {
}
public DbSet<Bar> Bar {
get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
[Table("Bar")]
public class Bar
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id {
get; set; }
[StringLength(50)]
public string Value {
get; set; }
}
public class TransactionScopeTest
{
public DbContextOptions<FooDbContext> FooOptions;
public DbContextOptions<BarDbContext> BarOptions;
public TransactionScopeTest()
{
FooOptions = (new DbContextOptionsBuilder<FooDbContext>()).UseSqlServer("server=localhost;database=Learning;integrated security=true;").Options;
BarOptions = (new DbContextOptionsBuilder<BarDbContext>()).UseSqlServer("server=localhost;database=Learning;integrated security=true;").Options;
}
public void Test()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions {
IsolationLevel = IsolationLevel.ReadCommitted }))
{
using (var fooContext = new FooDbContext(FooOptions))
{
var foo = new Foo {
Value = "Hi" };
fooContext.Add(foo);
fooContext.SaveChanges();
using (var barContext = new BarDbContext(BarOptions))
{
var bar = new Bar {
Value = "Loser" };
barContext.Add(bar);
barContext.SaveChanges();
}
scope.Dispose();
}
}
}
}
}
调用测试代码
new TransactionScopeTest().Test();
以上,
- 用了常用的 ReadCommitted 隔离级别。
- 上述代码走到第一个 SaveChanges 方法的时候,Foo 表锁住,其他查询语句会等待;走到第二个 SaveChanges 方法的时候,Bar 表锁住,其他查询语句会等待。
- Dispose 之后,表数据未更新,Foo 跟 Bar 两张表的其他查询语句正常执行。
再做个测试
using Microsoft.EntityFrameworkCore;
using System.Transactions;
using System.Linq;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace LocalAppTest
{
public class TransactionScopeTest
{
public DbContextOptions<FooDbContext> FooOptions;
public DbContextOptions<BarDbContext> BarOptions;
public TransactionScopeTest()
{
FooOptions = (new DbContextOptionsBuilder<FooDbContext>()).UseSqlServer("server=localhost;database=Learning;integrated security=true;").Options;
BarOptions = (new DbContextOptionsBuilder<BarDbContext>()).UseSqlServer("server=localhost;database=Learning;integrated security=true;").Options;
}
public void Test2()
{
var tasks = new List<Task>();
int i = 0;
while (i < 100)
{
tasks.Add(Task.Run(() =>
{
using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions {
IsolationLevel = IsolationLevel.ReadCommitted }))
{
using (var fooContext = new FooDbContext(FooOptions))
{
var records = fooContext.Foo.ToList();
var foo = new Foo {
Value = Guid.NewGuid().ToString() };
fooContext.Add(foo);
fooContext.SaveChanges();
scope.Complete();
}
}
}));
i++;
}
Task.WaitAll(tasks.ToArray());
}
}
}
调用测试代码
new TransactionScopeTest().Test2();
Test2方法里面启用了100个线程,每个线程做两件事情:1.查询Foo表的数据;2.新增一条数据。执行下来,ReadCommitted 隔离级别下,没有发生死锁的现象。
把上述隔离级别改成 Serializable 后,再次执行,当某个线程占用资源的时候,其他线程会抛出异常,不再执行。
先记录到这。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/161946.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...