跳到主要内容
版本:3.0.0

事务

事务定义在 Service 层。Repository 负责单表数据访问,Service 负责编排多个 Repository 的写入操作并控制事务边界。

推荐:UnitOfWork 工作单元

使用 Repository 做增删改查时,推荐使用 UnitOfWork 方式管理事务。

手动绑定工作单元

在 Service 层创建 UnitOfWork,把参与事务的仓储绑定到同一个工作单元:

using (var uow = _freeSql.CreateUnitOfWork())
{
_orderRepository.UnitOfWork = uow;
_orderItemRepository.UnitOfWork = uow;

try
{
await _orderRepository.UpdateAsync(order);
await _orderItemRepository.InsertAsync(item);

uow.Commit();
}
catch (Exception ex)
{
_logger.LogError(ex, "事务回滚");
uow.Rollback();
throw;
}
}

临时获取事务仓储

如果不想绑定已有仓储,也可以从 UnitOfWork 获取临时仓储:

using (var uow = _freeSql.CreateUnitOfWork())
{
var tempOrderRepo = uow.GetRepository<OrderEntity>();

try
{
await tempOrderRepo.UpdateAsync(order);
uow.Commit();
}
catch (Exception ex)
{
_logger.LogError(ex, "事务回滚");
uow.Rollback();
throw;
}
}

事务中使用 IFreeSql

事务内需要动态查询或写入时,使用仓储的 this.Ormuow.Orm,不要使用外部注入的 IFreeSql

错误:外部 _freeSql 不在事务内

using (var uow = _freeSql.CreateUnitOfWork())
{
// 这行操作不在事务内!
_freeSql.Insert(new OrderEntity()).ExecuteAffrows();
}

正确:通过绑定的仓储或 uow 访问

using (var uow = _freeSql.CreateUnitOfWork())
{
_orderRepository.UnitOfWork = uow;
var tempUserRepo = uow.GetRepository<UserEntity>();

// 以下操作都在事务内
_orderRepository.Orm.Insert(new OrderEntity()).ExecuteAffrows();
tempUserRepo.Orm.Update(new UserEntity()).ExecuteAffrows();
uow.Orm.Insert(new OrderEntity()).ExecuteAffrows();
}

Repository 中的写法

Repository 层不需要关心事务。单表操作直接用 this,多表联查用 this.Orm

public class OrderRepository : BaseRepository<OrderEntity, long>
{
public OrderRepository(IFreeSql<AegisDb> fsql) : base(fsql)
{
}

// 单表操作直接用 this
public async Task<OrderEntity> GetByIdAsync(long id)
{
return await this.Where(x => x.Id == id).FirstAsync();
}

// 多表联查用 this.Orm
public async Task<decimal> GetTotalAmount(long orderId)
{
return await this.Orm
.Select<OrderEntity, OrderItemEntity>()
.LeftJoin((o, i) => o.Id == i.OrderId)
.Where((o, i) => o.Id == orderId)
.SumAsync((o, i) => i.Amount);
}
}

Service 层在编排时绑定事务,Repository 本身不需要知道自己在不在事务里。

传统事务 DbTransaction

更建议使用 UnitOfWork。传统 DbTransaction 的表耦合性高,后续扩展分布式事务不方便。

同时不推荐使用异步事务 BeginTransactionAsync。异步事务不能确保事务的及时性,代码中存在 NoLock 时,异步事务执行的同时执行查询容易出现幻读。

using Object<DbConnection> conn = _freeSql.Ado.MasterPool.Get();
using DbTransaction transaction = conn.Value.BeginTransaction();
try
{
await fsql.Update<OrderEntity>()
.WithTransaction(transaction)
.Set(a => a.Status == 1)
.ExecuteAffrowsAsync();

transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}

最佳实践

事务放在 Service 层

事务的边界应该和业务操作的边界一致。Service 方法里编排多个 Repository 的写入,在 Service 层创建 UnitOfWork 并 Commit/Rollback。

尽量缩短事务范围

事务内的操作应该只有数据库写入,不要在事务里做耗时的外部调用(HTTP 请求、文件读写等)。事务持有时间越长,锁竞争越严重。

不要嵌套事务

一个 Service 方法里只创建一个 UnitOfWork。如果多个 Service 方法需要共享事务,把 UnitOfWork 作为参数传递,而不是各自创建。

提交后及时释放

使用 using 确保 UnitOfWork 及时释放:

using (var uow = _freeSql.CreateUnitOfWork())
{
// 操作
uow.Commit();
}

异常时回滚

UnitOfWorkCommit 之后的异常不会触发回滚。确保所有数据库操作都在 Commit 之前完成,异常在 catch 中回滚。

边界与限制

  • UnitOfWork 只对绑定了它的仓储生效。未绑定的仓储操作不在事务内
  • 一个 UnitOfWork 只能关联一个 IFreeSql 实例(一个数据库)
  • 分布式事务(TCC/Saga)暂未集成