基于前面对Action Filter的介绍,本节通过实例来说明其用法。
数据库事务有一个非常重要的特性,那就是“原子性”,它保证了我们对数据库的多个操作要么全部成功、要么全部失败,进而帮助我们保证业务数据的正确性。
下面代码将实现一个对于数据库操作的自动启用事务的操作筛选器。
使用TransactionScope简化事务代码的编写,TransactionScope是.Net中用来标记一段支持事务的代码的类。EF Core对TransactionScope提供了天然的支持,当一段使用EF Core进行数据库操作的代码放到TransactionScope声明的范围中的时候,这段代码就会自动被标记为“支持事务”。
大多数情况下,操作方法都需要有事务,当一个操作方法不需要自动启用事务控制时,可以给这些操作方法添加一个自定义的NotTransactionalAttribute特性。
NotTransactionalAttribute.cs代码如下:
/// <summary>
/// 自定义特性,在不需要自动启用事务控制的方法上添加该特性
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class NotTransactionalAttribute : Attribute
{
}
编写过滤器TransactionScopeFilter,代码如下:
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Reflection;
using System.Transactions;
namespace CoreWebApi3
{
/// <summary>
/// 自动启用事务的操作筛选器
/// </summary>
public class TransactionScopeFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//判断操作方法上是否标记了NotTransactionalAttribute
bool hasNotTransactionalAttribute = false;
if (context.ActionDescriptor is ControllerActionDescriptor)
{
var actionDesc = (ControllerActionDescriptor)context.ActionDescriptor;
hasNotTransactionalAttribute = actionDesc.MethodInfo.IsDefined(typeof(NotTransactionalAttribute));
}
//如果标记了,则直接执行next
if (hasNotTransactionalAttribute)
{
await next();
return;
}
using (var txScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var result = await next();
if (result.Exception == null)//操作方法执行没有异常
{
txScope.Complete();//提交事务
}
}
}
}
}
在Program.cs中注册TransactionScopeFilter:
//设置全局的筛选器
builder.Services.Configure<MvcOptions>(option =>
{
option.Filters.Add<TransactionScopeFilter>();
});
注册代码要写在builder.Build()之前。
最后,在EF CORE插入数据的代码中插入记录:
[HttpPost]
public async Task Save() {
dbCtx.Book.Add(new Book { Id = Guid.NewGuid(), Name = "1", Price = 1 });
await dbCtx.SaveChangeAsync();
dbCtx.Book.Add(new Book { Id = Guid.NewGuid(), Name = "2", Price = 2 });
await dbCtx.SaveChangeAsync();
}
上面的代码能够正确的插入两条数据。如果在第一个SaveChangeAsync下面加入一行throw new Exception() 来抛出异常,再次执行SaveChangeAsync方法之后,就会发现数据库中没有插入记录。这说明第一个SaveChangeAsync执行后,虽然实现了插入数据,但是由于事务回滚,因此被插入的数据也被回滚了。