事务
事务定义在 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.Orm 或 uow.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();
}
异常时回滚
UnitOfWork 的 Commit 之后的异常不会触发回滚。确保所有数据库操作都在 Commit 之前完成,异常在 catch 中回滚。
边界与限制
UnitOfWork只对绑定了它的仓储生效。未绑定的仓储操作不在事务内- 一个
UnitOfWork只能关联一个IFreeSql实例(一个数据库) - 分布式事务(TCC/Saga)暂未集成