操作筛选器的 1 个应用实例:自动启用事务

操作筛选器的 1 个应用实例:自动启用事务 操作筛选器的 自动启用事务 个应用实例

image

前言

在数据库操作过程中,有一个概念是绕不开的,那就是事务。

事务能够确保一系列数据库操作要么全部成功提交,要么全部失败回滚,保证数据的一致性和完整性。

在 Asp.Net Core Web API 中,我们可以使用操作筛选器给所有的数据库操作 API 加上事务控制,省心又省力,效果还很好。

看看 Step By Step 步骤是如何实现上述功能的。

Step By Step 步骤

  1. 创建一个 ASP.NET Core Web API 项目

  2. 引用 EF Core 项目 BooksEFCore

  3. 打开 appsettings.json,添加数据库连接串

    {
      "Logging": {
    	"LogLevel": {
    	  "Default": "Information",
    	  "Microsoft.AspNetCore": "Warning"
    	}
      },
      "AllowedHosts": "*",
      "ConnectionStrings": {
    	"Default": "Server=(localdb)\\mssqllocaldb;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true"
      }
    }
    
  4. 创建一个自定义的 Attribute,用于给无需启用事务控制的操作方法

    [AttributeUsage(AttributeTargets.Method)]
    public class NotTransactionalAttribute:Attribute
    {
    
    }
    
  5. 编写自定义的操作筛选器 TransactionScopeFilter,用于自动启用事务控制(留意注释

    using Microsoft.AspNetCore.Mvc.Controllers;
    using Microsoft.AspNetCore.Mvc.Filters;
    using System.Reflection;
    using System.Transactions;
    
    public class TransactionScopeFilter : IAsyncActionFilter
    {
    	public async Task OnActionExecutionAsync(
    		ActionExecutingContext context, 
    		ActionExecutionDelegate next)
    	{
    		bool hasNotTransactionalAttribute = false;
    		if (context.ActionDescriptor is ControllerActionDescriptor)
    		{
    			var actionDesc = (ControllerActionDescriptor)context.ActionDescriptor;
    			//判断操作方法上是否标注了NotTransactionalAttribute
    			hasNotTransactionalAttribute = actionDesc.MethodInfo.IsDefined(typeof(NotTransactionalAttribute));
    		}
    
    		//如果操作方法标注了NotTransactionalAttribute,直接执行操作方法
    		if (hasNotTransactionalAttribute)
    		{
    			await next();
    			return;
    		}
    
    		//如果操作方法没有标注NotTransactionalAttribute,启用事务
    		using var txScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
    		var result = await next();
    		if (result.Exception == null)
    		{
    			txScope.Complete();
    		}
    	}
    }
    
  6. 打开 Program.cs,注册这个操作筛选器

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    
    builder.Services.AddControllers();
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    // 注册数据库服务
    builder.Services.AddDbContext<MyDbContext>(opt => {
    	string connStr = builder.Configuration.GetConnectionString("Default");
    	opt.UseSqlServer(connStr);
    });
    
    // 注册自动启用事务过滤器
    builder.Services.Configure<MvcOptions>(opt => { 
    	opt.Filters.Add<TransactionScopeFilter>();
    });
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
    	app.UseSwagger();
    	app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  7. 打开控制器,增加一个用于测试的操作方法(留意注释

    using Microsoft.AspNetCore.Mvc;
    
    namespace 自动启用事务的筛选器.Controllers
    {
    	[ApiController]
    	[Route("[controller]/[action]")]
    	public class TestController : ControllerBase
    	{
    		private readonly MyDbContext dbCtx;
    
    		public TestController(MyDbContext dbCtx)
    		{
    			this.dbCtx = dbCtx;
    		}
    
    		[HttpPost]
    		public async Task Save()
    		{
    			dbCtx.Books.Add(new Book { Id = Guid.NewGuid(), Name = "1", Price = 1 });
    			await dbCtx.SaveChangesAsync();
    			dbCtx.Books.Add(new Book { Id = Guid.NewGuid(), Name = "2", Price = 2 });
    			await dbCtx.SaveChangesAsync();
    			// 以上代码能够正确地插入两条数据
    			// 如果启用以下代码抛出异常,将不会插入数据
    			// 说明事务起作用,数据被回滚了
    			// throw new Exception();
    		}
    	}
    }
    

总结

  1. 可以使用 TransactionScope 简化事务代码的编写。

  2. TransactionScope 是 .NET 中用来标记一段支持事务的代码的类。

  3. EF CoreTransactionScope 提供了天然的支持,当一段使用 EF Core 进行数据库操作的代码放到 TransactionScope 声明的范围中的时候,这段代码就会自动被标记为 "支持事务"

  4. TransactionScope 实现了 IDisposable 接口,如果一个 TransactionScope 的对象没有调用 Complete 就执行了 Dispose 方法,则事务会被回滚,否则事务就会被提交

  5. TransactionScope 还支持嵌套式事务,也就是多个 TransactionScope 嵌套,只有最外层的 TransactionScope 提交了事务,所有的操作才生效;如果最外层的 TransactionScope 回滚了事务,那么即使内层的 TransactionScope 提交了事务,最终所有的操作仍然会被回滚

  6. .NET Core 使用的 TransactionScope 支持的是 "最终一致性"。所谓的 "最终一致性",指的是在一段时间内,如果系统没有发生新的更新操作,那么所有副本的数据最终会达到一致的状态。换句话说,即使在系统中的不同节点上,数据的更新可能会有一段时间的延迟,但最终所有节点的数据会达到一致的状态。

  7. 在同步代码中,TransactionScope 使用 ThreadLocal 关联事务信息;

  8. 在异步代码中,TransactionScope 使用 AsyncLocal 关联事务信息

我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得!欢迎关注老杨的公众号(名称:代码掌控者),和你共同探索代码世界的奥秘!

image

评论