事务

1、UnitOfWork 事务

  1. using (var uow = fsql.CreateUnitOfWork()) {
  2. var songRepo = fsql.GetRepository<Song>();
  3. var userRepo = fsql.GetRepository<User>()
  4. songRepo.UnitOfWork = uow;
  5. userRepo.UnitOfWork = uow;
  6. songRepo.Insert(new Song());
  7. userRepo.Update(...);
  8. uow.Orm.Insert(new Song()).ExecuteAffrows();
  9. //注意:uow.Orm 和 fsql 都是 IFreeSql
  10. //uow.Orm CRUD 与 uow 是一个事务(理解为临时 IFreeSql)
  11. //fsql CRUD 与 uow 不在一个事务
  12. uow.Commit();
  13. }

参考:在 asp.net core 中使用 TransactionalAttribute + UnitOfWorkManager 实现多种事务传播

2、DbContext 事务

  1. using (var ctx = fsql.CreateDbContext()) {
  2. var song = ctx.Set<Song>();
  3. var user = ctx.Set<User>();
  4. song.Add(new Song());
  5. user.Update(...);
  6. ctx.SaveChanges();
  7. }

3、同线程事务

同线程事务,由 fsql.Transaction 管理事务提交回滚(缺点:不支持异步),比较适合 WinForm/WPF UI 主线程使用事务的场景。

用户购买了价值100元的商品:扣余额、扣库存。

  1. fsql.Transaction(() => {
  2. //fsql.Ado.TransactionCurrentThread 获得当前事务对象
  3. var affrows = fsql.Update<User>()
  4. .Set(a => a.Wealth - 100)
  5. .Where(a => a.Wealth >= 100).ExecuteAffrows();
  6. //判断别让用户余额扣成负数
  7. if (affrows < 1)
  8. throw new Exception("用户余额不足");
  9. //抛出异常,回滚事务,事务退出
  10. affrows = fsql.Update<Goods>()
  11. .Set(a => a.Stock - 1)
  12. .Where(a => a.Stock >= 1).ExecuteAffrows();
  13. //判断别让用库存扣成负数
  14. if (affrows < 1)
  15. throw new Exception("商品库存不足");
  16. //抛出异常,回滚事务,事务退出
  17. });

同线程事务使用简单,需要注意的限制:

  • 事务对象在线程挂载,每个线程只可开启一个事务连接,嵌套使用的是同一个事务;

  • 事务体内代码不可以切换线程,因此不可使用任何异步方法,包括FreeSql提供的数据库异步方法(可以使用任何 Curd 同步方法);

4、外部事务

在与其他开源项目一起使用时,事务由外部开启,使用 WithTransaction 将事务对象传入执行。

  1. await fsql.Update<xxx>()
  2. .WithTransaction(指定事务)
  3. .Set(a => a.Clicks + 1)
  4. .ExecuteAffrowsAsync();

ISelect、IInsert、IUpdate、IDelete,都支持 WithTransaction 方法。

5、更新排他锁

  1. var user = fsql.Select<User>().ForUpdate(true).Where(a => a.Id == 1).ToOne();
  2. //SELECT ... FROM User a for update nowait

for update 在 Oracle/PostgreSQL/MySql 是通用的写法,我们对 SqlServer 做了特别适配,执行的 SQL 语句大致如下:

  1. SELECT ... FROM [User] a With(UpdLock, RowLock, NoWait)

6、示范代码

  1. fsql.Transaction(() => //全局线程事务
  2. {
  3. fsql.Insert(new Products()).ExecuteAffrows();
  4. fsql.Insert(new Products()).ExecuteAffrows();
  5. });
  6. using (var uow = fsql.CreateUnitOfWork()) //使用 UnitOfWork 事务
  7. {
  8. fsql.Insert(new Products()).ExecuteAffrows(); //错误,没有传事务
  9. fsql.Insert(new Products()).WithTransaction(uow.GetOrBeginTransaction()).ExecuteAffrows();
  10. fsql.Insert(new Products()).WithTransaction(uow.GetOrBeginTransaction()).ExecuteAffrows();
  11. var repo1 = uow.GetRepository<Products>();
  12. repo1.Insert(new Products());
  13. }
  14. using (var ctx = fsql.CreateDbContext()) //使用 DbContext 事务
  15. {
  16. ctx.Add(new Products());
  17. ctx.Add(new Products());
  18. fsql.Insert(new Products()).ExecuteAffrows(); //错误,没有传事务
  19. fsql.Insert(new Products()).WithTransaction(ctx.UnitOfWork.GetOrBeginTransaction()).ExecuteAffrows(); //正常
  20. }
  21. using (var uowManager = new UnitOfWorkManager(fsql)) //使用 UnitOfWorkManager 管理类事务
  22. {
  23. using (var uow = uowManager.Begin())
  24. {
  25. fsql.Insert(new Products()).ExecuteAffrows(); //错误,没有传事务
  26. fsql.Insert(new Products()).WithTransaction(uow.GetOrBeginTransaction()).ExecuteAffrows();
  27. fsql.Insert(new Products()).WithTransaction(uow.GetOrBeginTransaction()).ExecuteAffrows();
  28. using (var uow2 = uowManager.Begin()) //与 uow 同一个事务
  29. {
  30. var repo1 = fsql.GetRepository<Products>();
  31. repo1.UnitOfWork = uow2;
  32. repo1.Insert(new Products());
  33. uow2.Commit();//事务还未提交
  34. }
  35. uow.Commit();//事务提交
  36. }
  37. }