西汉水 发表于 2024-10-11 22:37:57

操作筛选器的 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,用于给无需启用事务控制的操作方法

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
{
       
        /")]
        public class TestController : ControllerBase
        {
                private readonly MyDbContext dbCtx;

                public TestController(MyDbContext dbCtx)
                {
                        this.dbCtx = dbCtx;
                }

               
                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年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得!欢迎关注老杨的公众号(名称:代码掌控者),和你共同探索代码世界的奥秘!


来源:https://www.cnblogs.com/JackyGz/p/18458974
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 操作筛选器的 1 个应用实例:自动启用事务