操作筛选器的 1 个应用实例:自动启用事务
前言
在数据库操作过程中,有一个概念是绕不开的,那就是事务。
事务能够确保一系列数据库操作要么全部成功提交,要么全部失败回滚,保证数据的一致性和完整性。
在 Asp.Net Core Web API 中,我们可以使用操作筛选器给所有的数据库操作 API 加上事务控制,省心又省力,效果还很好。
看看 Step By Step 步骤是如何实现上述功能的。
Step By Step 步骤
-
创建一个 ASP.NET Core Web API 项目
-
引用 EF Core 项目 BooksEFCore
- BooksEFCore 项目创建参见前文《EF Core 在实际开发中,如何分层?》
-
打开
appsettings.json
,添加数据库连接串{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Default": "Server=(localdb)\\mssqllocaldb;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true" } }
-
创建一个自定义的 Attribute,用于给无需启用事务控制的操作方法
[AttributeUsage(AttributeTargets.Method)] public class NotTransactionalAttribute:Attribute { }
-
编写自定义的操作筛选器
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(); } } }
-
打开 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();
-
打开控制器,增加一个用于测试的操作方法(留意注释)
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(); } } }
总结
-
可以使用
TransactionScope
简化事务代码的编写。 -
TransactionScope
是 .NET 中用来标记一段支持事务的代码的类。 -
EF Core
对TransactionScope
提供了天然的支持,当一段使用EF Core
进行数据库操作的代码放到TransactionScope
声明的范围中的时候,这段代码就会自动被标记为 "支持事务" -
TransactionScope
实现了IDisposable
接口,如果一个TransactionScope
的对象没有调用Complete
就执行了Dispose
方法,则事务会被回滚,否则事务就会被提交 -
TransactionScope
还支持嵌套式事务,也就是多个TransactionScope
嵌套,只有最外层的TransactionScope
提交了事务,所有的操作才生效;如果最外层的TransactionScope
回滚了事务,那么即使内层的TransactionScope
提交了事务,最终所有的操作仍然会被回滚 -
.NET Core
使用的TransactionScope
支持的是 "最终一致性"。所谓的 "最终一致性",指的是在一段时间内,如果系统没有发生新的更新操作,那么所有副本的数据最终会达到一致的状态。换句话说,即使在系统中的不同节点上,数据的更新可能会有一段时间的延迟,但最终所有节点的数据会达到一致的状态。 -
在同步代码中,
TransactionScope
使用ThreadLocal
关联事务信息; -
在异步代码中,
TransactionScope
使用AsyncLocal
关联事务信息
我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得!欢迎关注老杨的公众号(名称:代码掌控者),和你共同探索代码世界的奥秘!