mirror of
https://github.com/dotnetcore/FreeSql.git
synced 2026-02-27 10:40:59 +08:00
1
285
事务.md
285
事务.md
@@ -1,72 +1,131 @@
|
||||
本文所有内容基于单机数据库事务,分布式数据库 TCC/SAGA 方案请移步:https://github.com/2881099/FreeSql.Cloud
|
||||
# 事务Transaction
|
||||
|
||||
## 0、[ASP.NET Core配置DI使用UnitOfWorkManager,此方法更简单](DI-UnitOfWorkManager)
|
||||
::: code-tabs
|
||||
|
||||
## 1、UnitOfWork 事务
|
||||
@tab:active .NET CLI
|
||||
|
||||
提示:工作单元依赖 FreeSql.DbContext.dll,后面 fsql.Transaction 同线程事务内置无依赖。
|
||||
```bash
|
||||
dotnet add package FreeSql.DbContext
|
||||
```
|
||||
|
||||
@tab Package Manager
|
||||
|
||||
```bash
|
||||
Install-Package FreeSql.DbContext
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 1、常规事务
|
||||
|
||||
UnitOfWork 是对 DbTransaction 事务对象的封装,方便夹带私有数据。
|
||||
|
||||
```csharp
|
||||
using (var uow = fsql.CreateUnitOfWork())
|
||||
{
|
||||
var songRepo = fsql.GetRepository<Song>();
|
||||
var userRepo = fsql.GetRepository<User>();
|
||||
songRepo.UnitOfWork = uow; //手工绑定工作单元
|
||||
userRepo.UnitOfWork = uow;
|
||||
await uow.Orm.Insert(item).ExecuteAffrowsAsync(); //uow.Orm API 和 IFreeSql 一样
|
||||
await uow.Orm.Ado.ExecuteNoneQueryAsync(sql);
|
||||
|
||||
songRepo.Insert(new Song());
|
||||
userRepo.Update(...);
|
||||
await fsql.Insert(item)... //错误,不在一个事务
|
||||
|
||||
uow.Orm.Insert(new Song()).ExecuteAffrows();
|
||||
//注意:uow.Orm 和 fsql 都是 IFreeSql
|
||||
//uow.Orm CRUD 与 uow 是一个事务(理解为临时 IFreeSql)
|
||||
//fsql CRUD 与 uow 不在一个事务
|
||||
var repo = uow.GetRepository<Song>(); //仓储 CRUD
|
||||
await repo.InsertAsync(item);
|
||||
|
||||
uow.Commit();
|
||||
uow.Commit();
|
||||
}
|
||||
```
|
||||
|
||||
## 2、DbContext 事务
|
||||
> 提示:uow 范围内,尽量别使用 fsql 对象,以免不处在一个事务
|
||||
|
||||
使用 UnitOfWorkManager 管理 UnitOfWork,如下:
|
||||
|
||||
```csharp
|
||||
using (var ctx = fsql.CreateDbContext()) {
|
||||
var song = ctx.Set<Song>();
|
||||
var user = ctx.Set<User>();
|
||||
|
||||
song.Add(new Song());
|
||||
user.Update(...);
|
||||
|
||||
ctx.SaveChanges();
|
||||
using (var uowManager = new UnitOfWorkManager(fsql))
|
||||
{
|
||||
using (var uow = uowManager.Begin())
|
||||
{
|
||||
using (var uow2 = uowManager.Begin()) //与 uow 同一个事务
|
||||
{
|
||||
uow2.Commit(); //事务还未提交
|
||||
}
|
||||
uow.Commit(); //事务提交
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2、仓储事务(依赖注入)
|
||||
|
||||
```csharp
|
||||
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;
|
||||
|
||||
public SongService(IBaseRepository<Song> songRepository, IBaseRepository<Detail> detailRepository)
|
||||
{
|
||||
_songRepository = songRepository;
|
||||
_detailRepository = detailRepository;
|
||||
}
|
||||
|
||||
[Transactional]
|
||||
async public Task Test1()
|
||||
{
|
||||
//所有注入的仓储对象,都是一个事务
|
||||
await _songRepository.InsertAsync(xxx1);
|
||||
await _detailRepository.DeleteAsync(xxx2);
|
||||
this.Test2();
|
||||
}
|
||||
|
||||
[Transactional(Propagation = Propagation.Nested)]
|
||||
public void Test2() //嵌套事务
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
具体请移步文档:- [AOP 特性标签实现跨方法事务](unitofwork-manager.md)
|
||||
|
||||
## 3、同线程事务
|
||||
|
||||
同线程事务,由 fsql.Transaction 管理事务提交回滚(缺点:不支持异步),比较适合 WinForm/WPF UI 主线程使用事务的场景。
|
||||
同线程事务内置在 FreeSql.dll,由 fsql.Transaction 管理事务提交回滚(缺点:不支持异步)。
|
||||
|
||||
用户购买了价值100元的商品:扣余额、扣库存。
|
||||
用户购买了价值 100 元的商品:扣余额、扣库存。
|
||||
|
||||
```csharp
|
||||
fsql.Transaction(() => {
|
||||
//fsql.Ado.TransactionCurrentThread 获得当前事务对象
|
||||
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("用户余额不足");
|
||||
//抛出异常,回滚事务,事务退出
|
||||
var affrows = fsql.Update<User>()
|
||||
.Set(a => a.Wealth - 100)
|
||||
.Where(a => a.Wealth >= 100).ExecuteAffrows();
|
||||
//判断别让用户余额扣成负数
|
||||
|
||||
affrows = fsql.Update<Goods>()
|
||||
.Set(a => a.Stock - 1)
|
||||
.Where(a => a.Stock >= 1).ExecuteAffrows();
|
||||
//判断别让用库存扣成负数
|
||||
|
||||
if (affrows < 1)
|
||||
throw new Exception("商品库存不足");
|
||||
//抛出异常,回滚事务,事务退出
|
||||
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("商品库存不足");
|
||||
});
|
||||
```
|
||||
|
||||
@@ -74,74 +133,9 @@ fsql.Transaction(() => {
|
||||
|
||||
- 事务对象在线程挂载,每个线程只可开启一个事务连接,嵌套使用的是同一个事务;
|
||||
|
||||
- 事务体内代码不可以切换线程,因此不可使用任何异步方法,包括FreeSql提供的数据库异步方法(可以使用任何 Curd 同步方法);
|
||||
- 事务体内代码不可以切换线程,因此不可使用任何异步方法,包括 FreeSql 提供的数据库异步方法(可以使用任何 Curd 同步方法);
|
||||
|
||||
## 4、外部事务
|
||||
|
||||
在与其他开源项目一起使用时,事务由外部开启,使用 WithTransaction 将事务对象传入执行。
|
||||
|
||||
```csharp
|
||||
await fsql.Update<xxx>()
|
||||
.WithTransaction(指定事务)
|
||||
.Set(a => a.Clicks + 1)
|
||||
.ExecuteAffrowsAsync();
|
||||
```
|
||||
|
||||
ISelect、IInsert、IUpdate、IDelete,都支持 WithTransaction 方法。
|
||||
|
||||
### 获取DbTransaction
|
||||
|
||||
- 异步方法
|
||||
|
||||
```csharp
|
||||
using Object<DbConnection> conn = await _freeSql.Ado.MasterPool.GetAsync();
|
||||
await using DbTransaction transaction = await conn.Value.BeginTransactionAsync();
|
||||
```
|
||||
|
||||
- 同步方法(using新语法)
|
||||
|
||||
```csharp
|
||||
using Object<DbConnection> conn = _freeSql.Ado.MasterPool.Get();
|
||||
using DbTransaction transaction = conn.Value.BeginTransaction();
|
||||
```
|
||||
|
||||
- 同步方法(using旧语法)
|
||||
|
||||
```csharp
|
||||
using(Object<DbConnection> conn = _freeSql.Ado.MasterPool.Get())
|
||||
{
|
||||
using(DbTransaction transaction = conn.Value.BeginTransaction())
|
||||
{
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例
|
||||
|
||||
```csharp
|
||||
public async Task CreateAsync(CreateGroupDto inputDto)
|
||||
{
|
||||
using Object<DbConnection> conn = _freeSql.Ado.MasterPool.Get();
|
||||
using DbTransaction transaction = conn.Value.BeginTransaction();
|
||||
try
|
||||
{
|
||||
await fsql.Update<xxx>()
|
||||
.WithTransaction(transaction)
|
||||
.Set(a => a.Clicks + 1)
|
||||
.ExecuteAffrowsAsync();
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
transaction.Rollback();
|
||||
//记录日志,抛出异常(使用全局异常)或者返回自定义数据
|
||||
throw;
|
||||
}
|
||||
}
|
||||
```
|
||||
## 5、更新排他锁
|
||||
## 4、悲观锁
|
||||
|
||||
```csharp
|
||||
var user = fsql.Select<User>().ForUpdate(true).Where(a => a.Id == 1).ToOne();
|
||||
@@ -154,77 +148,6 @@ for update 在 Oracle/PostgreSQL/MySql 是通用的写法,我们对 SqlServer
|
||||
SELECT ... FROM [User] a With(UpdLock, RowLock, NoWait)
|
||||
```
|
||||
|
||||
## 6、示范代码
|
||||
|
||||
```csharp
|
||||
//使用 全局线程事务
|
||||
fsql.Transaction(() =>
|
||||
{
|
||||
fsql.Insert(new Products()).ExecuteAffrows();
|
||||
fsql.Insert(new Products()).ExecuteAffrows();
|
||||
});
|
||||
|
||||
|
||||
//使用 UnitOfWork 事务
|
||||
using (var uow = fsql.CreateUnitOfWork())
|
||||
{
|
||||
var repo = uow.GetRepository<Products>();
|
||||
repo.Insert(new Products());
|
||||
|
||||
uow.Orm.Insert(new Products()).ExecuteAffrows(); //正常
|
||||
fsql.Insert(new Products()).ExecuteAffrows(); //错误,没有传事务
|
||||
fsql.Insert(new Products()).WithTransaction(uow.GetOrBeginTransaction()).ExecuteAffrows(); //正常
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
|
||||
//使用 DbContext 事务
|
||||
using (var ctx = fsql.CreateDbContext())
|
||||
{
|
||||
ctx.Add(new Products());
|
||||
|
||||
ctx.Orm.Insert(new Products()).ExecuteAffrows(); //正常
|
||||
fsql.Insert(new Products()).ExecuteAffrows(); //错误,没有传事务
|
||||
fsql.Insert(new Products()).WithTransaction(ctx.UnitOfWork.GetOrBeginTransaction()).ExecuteAffrows(); //正常
|
||||
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
|
||||
|
||||
//使用 UnitOfWorkManager 管理类事务
|
||||
using (var uowManager = new UnitOfWorkManager(fsql))
|
||||
{
|
||||
using (var uow = uowManager.Begin())
|
||||
{
|
||||
uow.Orm.Insert(new Products()).ExecuteAffrows(); //正常
|
||||
fsql.Insert(new Products()).ExecuteAffrows(); //错误,没有传事务
|
||||
fsql.Insert(new Products()).WithTransaction(uow.GetOrBeginTransaction()).ExecuteAffrows(); //正常
|
||||
|
||||
using (var uow2 = uowManager.Begin()) //与 uow 同一个事务
|
||||
{
|
||||
var repo1 = fsql.GetRepository<Products>();
|
||||
repo1.UnitOfWork = uow2;
|
||||
repo1.Insert(new Products());
|
||||
|
||||
uow2.Commit(); //事务还未提交
|
||||
}
|
||||
|
||||
uow.Commit(); //事务提交
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- IFreeSql Curd 方法若不是使用同线程事务,需要指定 WithTransaction 传入事务;
|
||||
- IUnitOfWork Orm 与工作单元事务一致;
|
||||
- FreeSql.IBaseRepository curd 方法需要指定 UnitOfWork 传递工作单元事务;
|
||||
- FreeSql.DbContext 自带事务;
|
||||
- UnitOfWorkManager 适合做跨方法事务;
|
||||
|
||||
[扩展阅读 1:IFreeSql 事务另类玩法,理解上面各种事务场景之后再看会更佳](https://github.com/dotnetcore/FreeSql/issues/322)
|
||||
|
||||
[扩展阅读 2:对以上各种事务的理解及演变](https://www.cnblogs.com/kellynic/p/13551855.html)
|
||||
|
||||
## 更多资料
|
||||
|
||||
- [《租户》](%e7%a7%9f%e6%88%b7)
|
||||
|
||||
Reference in New Issue
Block a user