Files
Docker/CloudBuilder/CloudBuilder.Core/DatabaseAccessor/UnitOfWorkAttribute.cs
T
2026-09-22 11:06:15 +08:00

122 lines
5.1 KiB
C#

using CloudBuilder.Core.Service;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace CloudBuilder.Core.DatabaseAccessor
{
/*
事务范围:TransactionScopeOption.RequiresNew 管理的是分布式事务,
TransactionScopeOption.Required但对于单个数据库连接,通常不需要分布式事务。
OnActionExecutionAsync 是 ASP.NET Core 中异步的 Action 过滤器执行方法。
// OnActionExecuting: Action 执行前
[开始事务]
↓
[Action 方法执行]
↓
// OnActionExecuted: Action 执行后(无论是否异常)
[检查异常]
//Controller对象不抛出异常,返回的是异常对象ValidatedException、ServiceErrorException(方便前端信息统一处理)
[提交或回滚事务]
注意(并发问题修复):
1) ActionFilter 的实例是「每个 Action 一份、被所有并发请求共用」的,不是每请求一个。
原来把 TransactionScope 存在实例字段 _transactionScope 上,两个请求重叠时:
A 请求刚写下自己的 scope,B 请求立刻把它覆盖成自己的 scope(在另一个线程上创建),
A 请求结束时 Dispose 的其实是 B 的 scope,于是抛
"A TransactionScope must be disposed on the same thread that it was created."
而且还会把别人的事务提前提交/回滚。现在改为按请求存放在 HttpContext.Items 里。
2) 创建时必须带 TransactionScopeAsyncFlowOption.Enabled,否则事务与创建线程强绑定,
Action 里 await 之后续体落到别的线程池线程,Dispose 同样会抛上面那条异常。
提交/回滚的判断逻辑(ValidatedException / ServiceErrorException → 回滚)保持原样未变。
*/
[AttributeUsage(AttributeTargets.Method)]
public class UnitOfWorkAttribute : ActionFilterAttribute
{
/// <summary>
/// 事务对象在 HttpContext.Items 里的键。
/// 用 HttpContext.Items 而不是实例字段:过滤器实例被所有并发请求共用,
/// 实例字段会让并发请求互相覆盖(详见类注释)。
/// </summary>
private const string ScopeKey = "__UnitOfWork_TransactionScope__";
public override void OnActionExecuting(ActionExecutingContext context)
{
// 同一请求内重复进入(嵌套)时复用外层事务,不重复开启
if (context.HttpContext.Items.ContainsKey(ScopeKey))
{
base.OnActionExecuting(context);
return;
}
// TransactionScopeAsyncFlowOption.Enabled:允许事务随 async/线程流转,
// 否则 Dispose 可能发生在另一个线程上而抛异常
var scope = new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TransactionManager.MaximumTimeout
},
TransactionScopeAsyncFlowOption.Enabled);
context.HttpContext.Items[ScopeKey] = scope;
base.OnActionExecuting(context);
}
public override void OnActionExecuted(ActionExecutedContext context)
{
// 取不到说明本请求没有开启(或已处理),直接放行
if (context.HttpContext.Items[ScopeKey] is not TransactionScope transactionScope)
{
base.OnActionExecuted(context);
return;
}
bool needRollback = false;
try
{
// ==============================================
// 核心逻辑:判断返回值是不是异常对象
// ==============================================
if (context.Result is ObjectResult objectResult)
{
var value = objectResult.Value;
if (value != null)
{
// 只要是这两个异常 → 必须回滚
if (value is ValidatedException || value is ServiceErrorException)
{
needRollback = true;
}
}
}
// 如果没有异常,才提交
if (!needRollback)
{
//必须要有 SaveChanges() 调用,否则数据只会停留在内存中,不会真正保存到数据库。
transactionScope.Complete();
}
}
finally
{
// 不 Complete() 就是自动回滚
transactionScope.Dispose();
context.HttpContext.Items.Remove(ScopeKey);
}
base.OnActionExecuted(context);
}
}
}