【数据事务】.NET开源 ORM 框架 SqlSugar 系列
.NET开源 ORM 框架 SqlSugar 系列
- 【开篇】.NET开源 ORM 框架 SqlSugar 系列
- 【入门必看】.NET开源 ORM 框架 SqlSugar 系列
- 【实体配置】.NET开源 ORM 框架 SqlSugar 系列
- 【Db First】.NET开源 ORM 框架 SqlSugar 系列
- 【Code First】.NET开源 ORM 框架 SqlSugar 系列
- 【数据事务】.NET开源 ORM 框架 SqlSugar 系列
- 【连接池】.NET开源 ORM 框架 SqlSugar 系列-CSDN博客
🔥数据库事务
数据库事务( transaction )是访问并可能操作各种 数据项 的一个数据库操作 序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
🟢事务特性
- 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。
- 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。
- 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
- 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。
1、单库事务
单库事务是针 一个db 操作执行的事务,无论是 ISqlSugarClient 和 SqlSugarClient 用法都一样
try
{
db.Ado.BeginTran();
db.Insertable(new Order() { .....}).ExecuteCommand();
db.Insertable(new Order() { .....}).ExecuteCommand();
db.Ado.CommitTran();
}
catch (Exception ex)
{
db.Ado.RollbackTran();
throw ex;
}
如果一个db就一个库,那么你也可以用多租户事务节约代码,因为2者在一个库的情况下作用一样。可以不写 .ado
db.BeginTran();//去掉了.ado
db.CommitTran();//去掉了.ado
db.RollbackTran();//去掉了.ado
//ISqlSugarClient 接口使用多租户事务 看文档2.2
2、多库事务(可跨库)
多数据库事务是SqlSugar独有的功能,稳定比 CAP 更强(CAP还有一层队列),在单个程序中可以很愉快的使用多库事务。
SqlSugarClient 或者 SqlSugarSope 继承于2个接口 ,代码如下事务:
SqlSugarClient : ISqlSugarClient, ITenant
多租户声明
SqlSugarClient db = new SqlSugarClient(new List<ConnectionConfig>()
{
new ConnectionConfig()
{ ConfigId="0", DbType=DbType.SqlServer,ConnectionString=..,IsAutoCloseConnection=true},
new ConnectionConfig()
{ ConfigId="1", DbType=DbType.MySql,ConnectionString=..,IsAutoCloseConnection=true}
});
划重点:简单的说多租户事务和单库事务用法基本100%一致,唯一区别就是少了.Ado
2.1 SqlSugarClient 事务
因为继承 ITenant 了可以直接使用 (老版本var mysql=db.GetConnection要写在事务外面)
🚫注意:禁止使用 db.Ado.BeginTran ,多租户是 db.BeginTran
//禁止使用 db.Ado.BeginTran,多租户是db.BeginTran
try
{
db.BeginTran();
db.GetConnection("1").Insertable(new Order() { }).ExecuteCommand();
db.GetConnection("0").Insertable(new Order() { }).ExecuteCommand();
db.CommitTran();
}
catch (Exception)
{
db.RollbackTran();//数据回滚
throw;
}
//主db
//注入的SqlSugarClient或者SqlSugarScope我们称为主db
//子db
//通过租户方法GetConnection出来的我们称为子db,用来操作当前数据库,没有租户事务相关方法
//主db可以用事务管理多个子db ,也可以使用 GetConnection等租户方法
//目前底层是业务执行成功后统一提交事务,业务只要失败全部回滚,统一回滚过程中都有有3次重试回滚
//从目前用户使用情况来看,相当稳定几乎没有一例失败的反馈
//高安全级别数据:请使用差异日志+Catch(AggregateException ex)进行补偿机质
//如果回滚失败会throw new AggregateException
2.2 ISqlSugarClient 事务
因为和ITenant没有继承关系,需要用 . AsTenAnt() 转换一下 。
db.AsTenant().BeginTran();//低版本 (db as ITenant).BeginTran()
db.AsTenant().CommitTran();
db.AsTenant().RollbackTran();
3、调试事务
db.ContextId 要从事务开始,CURD 和事务结束 必须一致 这个事务才会生效,如果是MYSQL也检查一下表引擎是否支持事务。
❓不一致怎么办
-
SqlsugarClient 可以用变量 var db=外部Db; 所有操作使用db保证一致。
-
SqlsuagrScope (该对象是线程安全对象,可以单例)可以用单例模式保证一致。
测试代码 最好用 Insert ,因为 Update 有条件过滤等因素添会添加测试难度,我们用插入来进行测试会比较简单些
try
{
db.BeginTran();
Console.WriteLine(db.Queryable<Order>().Count());//插入前数量
db.Insertable(new Order() { CreateTime=DateTime.Now, Name="aa",Price=1}).ExecuteCommand();
Console.WriteLine(db.Queryable<Order>().Count());//插入后数量
throw new Exception("出错");
db.CommitTran();
}
catch
{
db.RollbackTran();
Console.WriteLine(db.Queryable<Order>().Count());//回滚后数量
}
输出结果 为 155 156 155 插入前和回滚后一样就说明成功的。
4、语法糖
语法糖1: 5.0.4才支持
这种适合有 全局异常 的,直接出错扔出错并且回滚。
using (var tran = db.UseTran()){
//业务代码
//里面禁止写 try处理事务逻辑,格式一定按现在的风格来
tran.CommitTran();
}
//要写 try处理异常可以写在外面
语法糖2:自动异常
这种适合 没有异常 处理的,减少了try 处理
var result = db.UseTran(() =>
{
var beginCount = db.Queryable<Order>().ToList();
db.Ado.ExecuteCommand("delete Order");
var endCount = db.Queryable<Order>().Count();
return true;// 返回值等行result.Data
});
if (result.Data==false) //返回值为false
{
//result.Data 业务的返回值
//如果是上面的逻辑 result.Data==true肯定业务成功并且事务成功
//if(result.IsSuccess==false)//事务执行了回滚
//if(result.IsSuccess==true)//事务提交完成
}
语法糖3:工作单元
UnitOfWork: 工作单元模式/IUnitOfWorK/DbContext - SqlSugar 5x - .NET果糖网
5、跨方法事务
SqlSugarScope 单例模式支持跨事务方法。
SqlSugarClient 需要 IOC 设置 Scoped 周期实现。
6、CAP事务 TCC 和 Saga
6.1 原理讲解
CAP可以支持跨程序间的事务处理(非跨程序事务不建议用,涉及到队列等,在单程序中稳定性肯定不如自带的多租户事务)
注意: MySql用户使用 升级到最新
1、数据库的自动释放要关闭
2、手动打开数据库连接 db.Ado.Connection.Open();
3、用db.Ado.Connection创建事务
4、把你的事务赋值到ORM对象 db.Ado.Transaction = 你的事务;
5、执行你的代码
6、关闭Connection对象
//用户使用案例
var db=GetSqlsugarclient();
//关闭自动释放(需要 using手动释放)
db.CurrentConnectionConfig.IsAutoCloseConnection = false;
//取出ADO事务
using (var connection = (MySqlConnection)db.Ado.Connection)//SqlServer是SqlConnection
{
using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
{
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
db.Ado.Transaction = (IDbTransaction)transaction.DbTransaction;//这行很重要
db.Insertable<Test>(new Test()
{
name = DateTime.Now.ToString()
}).ExecuteCommand();
_capBus.Publish("Sample.RabbitMQ.MySql", DateTime.Now);
transaction.Commit();
}
}
7、异步事务
📢 注意:请不要在同步方法里面写下面方代码,必须是异步方法才行 返回是要带有Task async 。
用法1:
try
{
await db.BeginTranAsync();
await db.Insertable(new Order()
{
CreateTime = DateTime.Now,
CustomId = 1,
Name = "aaa",
Price = 0
}).ExecuteCommandAsync();
await db.CommitTranAsync();
}
catch (Exception)
{
await db.RollbackTranAsync();
}
用法2:
//只有5.0.3.8支持,老版本请升级使用
var res = await db.UseTranAsync(async () =>
{
await db.Insertable(new Order()
{
CreateTime = DateTime.Now,
CustomId = 1,
Name = "aaa",
Price = 0
}).ExecuteCommandAsync();
return true;//返回值会变成 res.Data
});
if (result.Data==false) //返回值为false
{
//result.Data 业务的返回值
//如果是上面的逻辑 result.Data==true肯定业务成功并且事务成功
//if(result.IsSuccess==false)//事务执行了回滚
//if(result.IsSuccess==true)//事务提交完成
}
//注意:
//await db.UseTranAsync 前面的await不要漏掉了
8、设置事务隔离级别
单库模式用法:
try
{
db.Ado.BeginTran(IsolationLevel.ReadCommitted);
//业务代码
db.Ado.CommitTran();
}
catch (Exception ex)
{
db.RollbackTran();
throw ex;
}
多租户模式:
var mysqlDb = db.GetConnection("mysql");
var mssqlDb = db.GetConnection("mssql");
try
{
mysqlDb.Ado.BeginTran(IsolationLevel.ReadCommitted);//开启库1的事务
mssqlDb.Ado.BeginTran(IsolationLevel.ReadCommitted);//开启库2的事务
//业务代码 只能用 mysqlDb和 mssqlDb
db.CommitTran();//注意不能用db.ado.CommitTran
}
catch (Exception ex)
{
db.RollbackTran();
throw ex;
}
9、嵌套事务(支持异步)
9.1. 共享模式
有外部事务,内部事务用外部事务(推荐)
通过 工作单元 实现嵌套事务 5.1.2.5-preview03
//db.Ado.IsNoTran()表示事务为null才开启事务
using (var uow = db.CreateContext(db.Ado.IsNoTran()))
{
using (var uow2 = db.CreateContext(db.Ado.IsNoTran()))
{
uow2.Commit();
}
uow.Commit(); //禁止用 db.RollBack 工作单元内只要throw会自动回滚
}
9.2. 子事务独立 (不推荐)
🚫不推荐原因:这种很容易出现坑,比如业务A和业务B都用到了一样的表就会死锁
事务嵌套:推荐用9.1或者标题10 ,9.2是不推荐的
try
{
db.BeginTran();
//业务..A
var childDb=db.CopyNew();
try
{
childDb.BeginTran();
//...业务B
childDb.Commit();
}
catch (Exception)
{
childDb.RollbackTran();//数据回滚
throw;
}
db.CommitTran();
}
catch (Exception)
{
db.RollbackTran();//数据回滚
throw;
}
10、工作单元【事务特性】
在 .NET Core 中,可以使用自定义的 ActionFilter 来封装事务。
[ServiceFilter(typeof(TransactionFilter))]//加上这行就可以用了
public IEnumerable<WeatherForecast> Get()
{
.....数据库操作...
}
10.1 创建全局事务
public class TransactionFilter : IActionFilter
{
ISqlSugarClient _db;//你也可以换EF CORE对象 或者ADO对象都行
public TransactionFilter(ISqlSugarClient db)//(ISqlSugarClient)需要IOC注入处理事务的对象
{
_db=db;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_db.AsTenant().BeginTran();//接口要加.AsTenant()
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception == null)
{
_db.AsTenant().CommitTran();
}
else
{
_db.AsTenant().RollBack();
}
}
}
10.2 IOC注册
//注入事务对象
builder.Services.AddScoped<TransactionFilter>();
//注入ORM对象
//注册上下文:AOP里面可以获取IOC对象,如果有现成框架比如Furion可以不写这一行
services.AddHttpContextAccessor();
//注册SqlSugar
services.AddSingleton<ISqlSugarClient>(s =>
{
SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
{
DbType = SqlSugar.DbType.Sqlite,
ConnectionString = "DataSource=sqlsugar-dev.db",
IsAutoCloseConnection = true,
},
db =>
{
//单例参数配置,所有上下文生效
db.Aop.OnLogExecuting = (sql, pars) =>
{
//获取作IOC作用域对象
//var appServive = s.GetService<IHttpContextAccessor>();
//var obj = appServive?.HttpContext?.RequestServices.GetService<Log>();
//Console.WriteLine("AOP" + obj.GetHashCode());
};
});
return sqlSugar;
});
10.3 在接口打上事务
[ServiceFilter(typeof(TransactionFilter))]//加上这行就可以用了
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
.....数据库操作...
}
11、MySql注意事项
(1)MYSQL不支持创建表和删除表处理事务,原生事务也一样 。
(2)MyISAM 存储引擎不支持事务 需要改成 InnoDB 。