FreeSql.Cloud
1.9.1
dotnet add package FreeSql.Cloud --version 1.9.1
NuGet\Install-Package FreeSql.Cloud -Version 1.9.1
<PackageReference Include="FreeSql.Cloud" Version="1.9.1" />
paket add FreeSql.Cloud --version 1.9.1
#r "nuget: FreeSql.Cloud, 1.9.1"
// Install FreeSql.Cloud as a Cake Addin #addin nuget:?package=FreeSql.Cloud&version=1.9.1 // Install FreeSql.Cloud as a Cake Tool #tool nuget:?package=FreeSql.Cloud&version=1.9.1
<h1 align="center"> 🦄 FreeSql.Cloud </h1>
为 FreeSql 提供跨数据库访问,分布式事务TCC、SAGA解决方案,支持 .NET Core 2.1+, .NET Framework 4.0+.
快速开始
dotnet add package FreeSql.Cloud
or
Install-Package FreeSql.Cloud
public enum DbEnum { db1, db2 }
public class FreeSqlCloud : FreeSqlCloud<DbEnum>
{
public FreeSqlCloud() : base(null) { }
public FreeSqlCloud(string distributeKey) : base(distributeKey) { }
}
var fsql = new FreeSqlCloud();
fsql.DistributeTrace = log => Console.WriteLine(log.Split('\n')[0].Trim());
fsql.Register(DbEnum.db1, () => new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, @"Data Source=db1.db").Build());
fsql.Register(DbEnum.db2, () => new FreeSqlBuilder().UseConnectionString(DataType.Sqlite, @"Data Source=db2.db").Build());
services.AddSingleton<IFreeSql>(fsql);
services.AddSingleton(fsql);
FreeSqlCloud 必须定义成单例模式
new FreeSqlCloud() 多连接管理,DbEnum 换成 string 就是多租户管理
new FreeSqlCloud("myapp") 开启 TCC/SAGA 事务生效
如何使用?
FreeSqlCloud 的访问方式和 IFreeSql 一样:
fsql.Select<T>();
fsql.Insert<T>();
fsql.Update<T>();
fsql.Delete<T>();
//...
切换数据库(多线程安全):
fsql.Change(DbEnum.db2).Select<T>();
//同一线程,或异步await 后续 fsql.Select/Insert/Update/Delete 操作是 db2
fsql.Use(DbEnum.db2).Select<T>();
//单次有效
using (fsql.Change(DbEnum.db2)) {
//todo..
}
//FreeSql.Cloud v1.6.8 一个范围内切换,之后再切换回去
自动定向数据库配置:
fsql.EntitySteering = (_, e) =>
{
if (e.EntityType == typeof(User)) e.DBKey = DbEnum.db2;
//查询 User 自动定向 db2
};
关于仓储对象 Repository
1、静态仓储对象
FreeSql.Repository/UnitOfWorkManager 对象创建时固定了 IFreeSql,因此无法跟随 FreeSqlCloud 切换数据库。
注意:是同一个对象实例创建之后,无法跟随切换,创建新对象实例不受影响。
租户分库场景 Repository/UnitOfWorkManager 创建之前,先调用 fsql.Change 切换好数据库。
《FreeSql.Cloud 如何使用 UnitOfWorkManager 实现 AOP 事务?》
2、动态创建对象(不推荐)
但是。。。仍然有一种特殊需求,Repository 在创建之后,仍然能跟随 fsql.Change 切换数据库。
var repo = DB.Cloud.GetCloudRepository<User>();
DB.Cloud.Change(DbEnum.db2);
Console.WriteLine(repo.Orm.Ado.ConnectionString); //repo -> db2
DB.Cloud.Change(DbEnum.db1);
Console.WriteLine(repo.Orm.Ado.ConnectionString); //repo -> db1
这种机制太不可控,所以只做了简单的扩展方法创建,并不推荐 Ioc 注入。
关于并发
FreeSqlCloud 内部使用 IdleBus + AsyncLocal<string> 方式实现,Change/Use 多线程并发是安全的。
FreeSqlCloud 实现了接口 IFreeSql,但它不负责直接交互数据库,只是个代理层。
public class FreeSqlCloud<TDBKey> : IFreeSql
{
AsyncLocal<TDBKey> _currentKey = new AsyncLocal<TDBKey>();
IFreeSql _current => _idlebus.Get(_currentKey.Value);
IdleBus<TDBKey, IFreeSql> _idlebus;
...
public IAdo Ado => _current.Ado;
public GlobalFilter GlobalFilter => _current.GlobalFilter;
public void Transaction(Action handler) => _current.Transaction(handler);
...
}
AsyncLocal 负责存储执行上下文 DBKey 值,在异步或同步并发场景是安全的,fsql.Change(DbEnum.db2) 会改变该值。fsql.Change/Use 方法返回 IFreeSql 特殊实现,大大降低 IdleBus 因误用被释放的异常(原因:IdleBus.Get 返回值不允许被外部变量长期引用,应每次 Get 获取对象)
关于分布式事务
1、简介
FreeSqlCloud 提供 TCC/SAGA 分布式事务调度、失败重试、持久化重启后重新唤醒事务单元、等管理功能。
TCC 事务特点:
- Try 用于资源冻结/预扣;
- Try 全部环节通过,代表业务一定能完成,进入 Confirm 环节;
- Try 任何环节失败,代表业务失败,进入 Cancel 环节;
- Confirm 失败会进行重试N次,直到交付成功,或者人工干预;
- Cancel 失败会进行重试N次,直到取消成功,或者人工干预;
// 测试数据
fsql.Use(DbEnum.db1).Insert(new User { Id = 1, Name = "testuser01", Point = 10 }).ExecuteAffrows();
fsql.Use(DbEnum.db2).Insert(new Goods { Id = 1, Title = "testgoods01", Stock = 0 }).ExecuteAffrows();
var orderId = Guid.NewGuid();
await fsql.StartTcc(orderId.ToString(), "支付购买",
new TccOptions
{
MaxRetryCount = 10,
RetryInterval = TimeSpan.FromSeconds(10)
})
.Then<Tcc1>(DbEnum.db1, new BuyUnitState { UserId = 1, Point = 10, GoodsId = 1, OrderId = orderId })
.Then<Tcc2>(DbEnum.db2, new BuyUnitState { UserId = 1, Point = 10, GoodsId = 1, OrderId = orderId })
.Then<Tcc3>(DbEnum.db2, new BuyUnitState { UserId = 1, Point = 10, GoodsId = 1, OrderId = orderId })
.ExecuteAsync();
2022-08-16 10:47:53 【myapp】db1 注册成功, 并存储 TCC/SAGA 事务相关数据
2022-08-16 10:47:53 【myapp】成功加载历史未完成 TCC 事务 0 个
2022-08-16 10:47:53 【myapp】成功加载历史未完成 SAGA 事务 0 个
2022-08-16 10:47:53 【myapp】TCC (3a9c548f-95b1-43b4-b918-9c3817d4c316, 支付购买) Created successful, retry count: 10, interval: 10S
2022-08-16 10:47:53 【myapp】TCC (3a9c548f-95b1-43b4-b918-9c3817d4c316, 支付购买) Unit1(第1步:数据库db1 扣除用户积分) TRY successful
2022-08-16 10:47:53 【myapp】数据库使用[Use] db2
2022-08-16 10:47:53 【myapp】TCC (3a9c548f-95b1-43b4-b918-9c3817d4c316, 支付购买) Unit2(第2步:数据库db2 扣除库存) TRY failed, ready to CANCEL, -ERR 扣除库存失败
2022-08-16 10:47:53 【myapp】TCC (3a9c548f-95b1-43b4-b918-9c3817d4c316, 支付购买) Unit1(第1步:数据库db1 扣除用户积分) CANCEL successful
2022-08-16 10:47:53 【myapp】TCC (3a9c548f-95b1-43b4-b918-9c3817d4c316, 支付购买) Completed, all units CANCEL successfully
SAGA 事务特点:
- Commit 用于业务提交;
- Commit 全部环节通过,代表业务交付成功;
- Commit 任何环节失败,代表业务失败,进入 Cancel 环节;
- Cancel 失败会进行重试N次,直到取消成功,或者人工干预;
2、唯一标识
FreeSqlCloud 使用唯一标识区分,解决冲突问题,举例:
var fsql = new FreeSqlCloud("myapp");
var fsql2 = new FreeSqlCloud("myapp2");
fsql2 访问不到 fsql 产生的分布式事务,如果 webapi 部署多实例,只需要设置实例各自对应的 name 区分即可。
3、持久化
fsql.Register 第一个注册的称之为【主库】,存储 TCC/SAGA 持久数据,程序启动的时候,会将未处理完的事务载入内存重新调度。
自动创建表 tcc_myapp、saga_myapp:
提示:fsql2 会创建表 tcc_myapp2、saga_myapp2
字段名 | 描述 |
---|---|
tid | 事务ID |
title | 事务描述,查看日志更直观 |
total | 所有单元数量 |
create_time | 创建时间 |
finish_time | 完成时间 |
status | Pending, Confirmed, Canceled, ManualOperation |
max_retry_count | 最大重试次数,如果仍然失败将转为【人工干预】 |
retry_interval | 重试间隔(秒) |
retry_count | 已重试次数 |
retry_time | 最后重试时间 |
自动创建表 tcc_myapp_unit、saga_myapp_unit:
提示:fsql2 会创建表 tcc_myapp2_unit、saga_myapp2_unit
字段名 | 描述 |
---|---|
tid | 事务ID |
index | 单元下标,1到N |
description | 单元描述,使用 [Description("xx")] 特性设置,查看日志更直观 |
stage | Try, Confirm, Cancel |
type_name | 对应 c# TccUnit/SagaUnit 反射类型信息,用于创建 TccUnit/SagaUnit 对象 |
state | 状态数据 |
state_type_name | 状态数据对应的 c# 反射类型信息 |
create_time | 创建时间 |
db_key | 用于唤醒时使用 fsql.Use(db_key) 对应的事务或开启事务 |
其他库会创建表 myapp_unit_invoked 判断重复执行
4、单元
TccUnit、SagaUnit 方法内可以使用 Orm 访问当前事务对象。
单元方法除了操作数据库,也支持远程访问 webapi/grpc,发生异常时触发重试调度。由于网络不确定因素,较坏的情况比如单元调用 webapi/grpc 成功,但是 tcc_unit 表保存状态失败,导致单元又会重试执行,所以 web/grpc 提供方应该保证幂等操作,无论多少次调用结果都一致。
// HTTP 服务编排??
var orderId = Guid.NewGuid();
await DB.Cloud.StartSaga(orderId.ToString(), "支付购买webapi(saga)",
new SagaOptions
{
MaxRetryCount = 10,
RetryInterval = TimeSpan.FromSeconds(10)
})
.Then<HttpSaga>(default, new HttpUnitState
{
Url = "https://192.168.1.100/saga/UserPoint",
Data = "UserId=1&Point=10&GoodsId=1&OrderId=" + orderId
})
.Then<HttpSaga>(default, new HttpUnitState
{
Url = "https://192.168.1.100/saga/GoodsStock",
Data = "UserId=1&Point=10&GoodsId=1&OrderId=" + orderId
})
.Then<HttpSaga>(default, new HttpUnitState
{
Url = "https://192.168.1.100/saga/OrderNew",
Data = "UserId=1&Point=10&GoodsId=1&OrderId=" + orderId
})
.ExecuteAsync();
class HttpSaga : SagaUnit<HttpUnitState>
{
public override Task Commit()
{
//Console.WriteLine("请求 webapi:" + State.Url + "/Commit" + State.Data);
return Task.CompletedTask;
}
public override Task Cancel()
{
//Console.WriteLine("请求 webapi:" + State.Url + "/Cancel" + State.Data);
return Task.CompletedTask;
}
}
class HttpUnitState
{
public string Url { get; set; }
public string Data { get; set; }
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net40 is compatible. net403 was computed. net45 was computed. net451 was computed. net452 was computed. net46 was computed. net461 is compatible. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.0
- FreeScheduler (>= 2.0.18)
- FreeSql.DbContext (>= 3.2.808)
- IdleBus (>= 1.5.3)
- Newtonsoft.Json (>= 13.0.1)
-
.NETFramework 4.6.1
- FreeScheduler (>= 2.0.18)
- FreeSql.DbContext (>= 3.2.808)
- IdleBus (>= 1.5.3)
- Newtonsoft.Json (>= 13.0.1)
-
.NETStandard 2.0
- FreeScheduler (>= 2.0.18)
- FreeSql.DbContext (>= 3.2.808)
- IdleBus (>= 1.5.3)
- Newtonsoft.Json (>= 13.0.1)
NuGet packages (16)
Showing the top 5 NuGet packages that depend on FreeSql.Cloud:
Package | Downloads |
---|---|
HandeSoft.Core.Abstract
Package Description |
|
RsCode
快速高效的开发.net就用RsCode,文档访问https://rscode.cn |
|
Athena.Infrastructure.FreeSql
ORM基于FreeSql的实现 |
|
ZhonTai.Admin
中台Admin权限管理接口库 |
|
JetyDu.BaseDB
Package Description |
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on FreeSql.Cloud:
Repository | Stars |
---|---|
leooneone/aibpm.plus
AIBPM是一个开源的工作流引擎。本项目是后端服务,前端请移步aibpm.ui.plus。
|
Version | Downloads | Last updated |
---|---|---|
1.9.1 | 5,898 | 7/15/2024 |
1.9.0 | 706 | 7/5/2024 |
1.8.3 | 5,844 | 2/5/2024 |
1.8.2 | 2,436 | 12/20/2023 |
1.8.1 | 249 | 12/18/2023 |
1.6.12 | 486 | 12/7/2023 |
1.6.6 | 7,800 | 5/30/2023 |
1.6.5 | 11,905 | 3/29/2023 |
1.6.4 | 3,098 | 3/14/2023 |
1.6.3 | 20,788 | 9/16/2022 |
1.6.2 | 453 | 9/13/2022 |
1.6.1 | 579 | 8/31/2022 |
1.5.3 | 433 | 8/29/2022 |
1.3.1 | 598 | 7/28/2022 |
1.3.0 | 499 | 7/28/2022 |
1.2.1 | 458 | 7/19/2022 |
1.2.0 | 1,536 | 7/4/2022 |
1.0.3 | 1,605 | 12/7/2020 |