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

前言

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

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

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

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

Step By Step 步骤

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

  2. 引用 EF Core 项目 BooksEFCore

    • BooksEFCore 项目创建参见前文《EF Core 在实际开发中,如何分层?》
  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(opt => {
    	string connStr = builder.Configuration.GetConnectionString("Default");
    	opt.UseSqlServer(connStr);
    });
    
    // 注册自动启用事务过滤器
    builder.Services.Configure(opt => { 
    	opt.Filters.Add();
    });
    
    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 关联事务信息

你可能感兴趣的:(C#,asp.net,core,c#,asp.net,core,经验分享)