事务操作(Tran / TranAsync)
IChameleonDbContext 的事务方法 Tran / TranAsync(又称"数据库事务 / 原子操作")将多个数据库操作包裹在同一事务中执行,保证原子性:要么全部提交,要么全部回滚。编写 C# 脚本和微服务代码时一律使用异步版本 TranAsync。
概述
- 事务范围以传入的 lambda 为界,lambda 内通过同一个
IChameleonDbContext实例执行的所有写操作都在该事务中 - 支持嵌套:事务内再调用
Tran/TranAsync不会开启新事务,而是引用计数加一;只有最外层 lambda 正常结束时才提交,任何一层抛异常且传播到最外层时回滚 - 事务内的写操作产生的数据变更审计日志会延迟到提交前统一写出;回滚时审计日志一并丢弃
- 事务中调用
GetDatabase()返回的是事务绑定的连接(UnitOfWork.Orm),保证同一连接执行 - 实例被释放(
Dispose)时若仍有未完成事务,会强制回滚 - 可用
InTransaction属性判断当前是否处于事务中
API 方法
// 异步版本(推荐)
Task TranAsync(Func<Task> func);
Task<TResult> TranAsync<TResult>(Func<Task<TResult>> func);
// 同步版本
void Tran(Action func);
TResult Tran<TResult>(Func<TResult> func);
// 是否处于事务中
bool InTransaction { get; }
| 参数 | 说明 |
|---|---|
func | 事务体委托;内部通过闭包访问同一个 dbContext 执行写操作 |
TResult | 带返回值版本,lambda 的返回值即方法返回值 |
使用示例
基本事务(订单 + 库存同成功同失败)
var db = sdk.DbContext;
await db.TranAsync(async () =>
{
await db.AddAsync("Order", orderData);
await db.UpdateAsync("Inventory", inventoryData);
// 两者在同一事务:任一失败则全部回滚
});
带返回值的事务
var result = await db.TranAsync(async () =>
{
await db.AddAsync("Order", orderData);
await db.AddAsync("OrderItem", items);
return new { ok = true };
});
嵌套事务
await db.TranAsync(async () =>
{
await db.AddAsync("Order", orderData);
// 内层不会开启新事务;内层异常会传播导致整体回滚
await db.TranAsync(async () =>
{
await db.AddAsync("OrderItem", items);
});
});
事务内抛异常回滚
await db.TranAsync(async () =>
{
await db.AddAsync("Order", orderData);
if (!stockEnough)
{
throw new Exception("库存不足"); // 触发回滚,Order 不会写入
}
await db.UpdateAsync("Inventory", inventoryData);
});
行为细节
- 嵌套通过引用计数实现:每进入一层加一,正常退出减一,减到
0时提交;异常时减到0才回滚并向上抛出原始异常 - 提交前会先刷出事务期间累积的数据变更审计日志,因此回滚不会产生审计记录
- 不要在事务 lambda 内使用
Task.Run/ 新开线程执行写操作——事务绑定当前上下文与连接,跨线程操作不在事务内 - 事务中避免执行长时间的外部调用(HTTP、消息队列等),占用数据库连接与锁
下一步
- 数据增删改与逻辑删除 - 事务中执行的主要操作
- 数据变更审计 - 审计日志与事务提交/回滚的关系
- SQL 查询与底层实例 -
GetDatabase()在事务中的行为
相关实现
- 事务实现:
chameleon-core/components/Chameleon.Database/DbContext/ChameleonDbContext.cs(基于 FreeSqlUnitOfWork,引用计数嵌套)