C# TransactionScope

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 后,再次执行,当某个线程占用资源的时候,其他线程会抛出异常,不再执行。

先记录到这。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值