跳至主要內容

事务Transaction

nicye2020年10月16日大约 3 分钟约 796 字

事务Transaction

.NET CLI
dotnet add package FreeSql.DbContext

1、常规事务

UnitOfWork 是对 DbTransaction 事务对象的封装,方便夹带私有数据。

using (var uow = fsql.CreateUnitOfWork())
{
 await uow.Orm.Insert(item).ExecuteAffrowsAsync(); //uow.Orm API 和 IFreeSql 一样
 await uow.Orm.Ado.ExecuteNoneQueryAsync(sql);

 await fsql.Insert(item)... //错误,不在一个事务

 var repo = uow.GetRepository<Song>(); //仓储 CRUD
 await repo.InsertAsync(item);

 uow.Commit();
}

提示:uow 范围内,尽量别使用 fsql 对象,以免不处在一个事务

使用 UnitOfWorkManager 管理 UnitOfWork,如下:

using (var uowManager = new UnitOfWorkManager(fsql))
{
 using (var uow = uowManager.Begin())
 {
 using (var uow2 = uowManager.Begin()) //与 uow 同一个事务
 {
 uow2.Commit(); //事务还未提交
 }
 uow.Commit(); //事务提交
 }
}

2、仓储事务(依赖注入)

var builder = WebApplication.CreateBuilder(args);
Func<IServiceProvider, IFreeSql> fsqlFactory = r =>
{
 IFreeSql fsql = new FreeSql.FreeSqlBuilder()
 .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=freedb.db")
 .UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}"))
 .Build();
 return fsql;
};
builder.Services.AddSingleton<IFreeSql>(fsqlFactory);

builder.Services.AddFreeRepository();
builder.Services.AddScoped<UnitOfWorkManager>();
builder.Services.AddScoped<SongService>();
WebApplication app = builder.Build();


public class SongService
{
 readonly IBaseRepository<Song> _songRepository;
 readonly IBaseRepository<Detail> _detailRepository;
 readonly UnitOfWorkManager _unitOfWorkManager;

 public SongService(
 IBaseRepository<Song> songRepository,
 IBaseRepository<Detail> detailRepository,
 UnitOfWorkManager unitOfWorkManager
 )
 {
 _songRepository = songRepository;
 _detailRepository = detailRepository;
 _unitOfWorkManager = unitOfWorkManager;
 }

 [Transactional]
 public async Task Test1()
 {
 //所有注入的仓储对象,都是一个事务
 await _songRepository.InsertAsync(xxx1);
 await _detailRepository.DeleteAsync(xxx2);
 this.Test2();
 }

 [Transactional(Propagation = Propagation.Nested)]
 public void Test2() //嵌套事务
 {
 }

 public async Task Test3()
 {
 using (var uow = _unitOfWorkManager.Begin())
 {
 await _songRepository.InsertAsync(xxx1);
 await _detailRepository.DeleteAsync(xxx2);
 uow.Commit();
 }
 }
}

具体请移步文档:- AOP 特性标签实现跨方法事务

3、同线程事务

同线程事务内置在 FreeSql.dll,由 fsql.Transaction 管理事务提交回滚(缺点:不支持异步)。

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

fsql.Transaction(() =>
{
 //fsql.Ado.TransactionCurrentThread 获得当前事务对象

 var affrows = fsql.Update<User>()
 .Set(a => a.Wealth - 100)
 .Where(a => a.Wealth >= 100).ExecuteAffrows();
 //判断别让用户余额扣成负数

 //抛出异常,回滚事务,事务退出
 if (affrows < 1) throw new Exception("用户余额不足");

 affrows = fsql.Update<Goods>()
 .Set(a => a.Stock - 1)
 .Where(a => a.Stock >= 1).ExecuteAffrows();

 if (affrows < 1) throw new Exception("商品库存不足");
});

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

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

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

4、悲观锁

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

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

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

5. 外部事务

// 场景:已经有一个开启的 Connection 和 Transaction
using (var conn = new SqlConnection("..."))
{
 conn.Open();
 using (var tran = conn.BeginTransaction())
 {
 // 1. 原生/Dapper 操作
 // command.Transaction = tran;
 // command.ExecuteNonQuery();

 // 2. 桥接给 FreeSql
 // 使用扩展方法创建适配器,传入现有的 tran
 using (var uow = fsql.CreateUnitOfWork(tran)) 
 {
 // 在此 uow 下获取的仓储或 Orm,都会使用传入的 tran
 var repo = uow.GetRepository<MyEntity>();
 repo.Insert(new MyEntity { Name = "FreeSql Insert" });

 // 或者直接使用 Orm
 uow.Orm.Insert(new MyEntity { Name = "Direct Orm Insert" }).ExecuteAffrows();

 // 这里的 Commit 只会触发 FreeSql 的事件,不会提交物理事务
 uow.Commit(); 
 }

 // 3. 真正的提交由最外层控制
 tran.Commit();
 }
}
最近更新
贡献者: taadis,luoyunchong,igeekfan,2881099,luozq,Mister-Hope,IGeekFan

AltStyle によって変換されたページ (->オリジナル) /