.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();//去掉了.adodb.CommitTran();//去掉了.adodb.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.BeginTrantry{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();//业务..Avar childDb=db.CopyNew();try{childDb.BeginTran();//...业务BchildDb.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 。