AOP、日志、拦截、差异日志

1、SQL事件

1.1 事件示例

注意:Aop一定要设置在你操作语句之前,不然不会生效,还有必须是同一个SqlSuagrClient才会有效

public  SqlSugarClient GetInstance()
{
    SqlSugarClient Db= new SqlSugarClient(new ConnectionConfig()
        {
            ConnectionString = "Server=.xxxxx",
            DbType = DbType.SqlServer,
            IsAutoCloseConnection = true,
            InitKeyType = InitKeyType.Attribute
        },db=>{
        
                  //如果是多库看标题6
        
             //每次Sql执行前事件
             db.Aop.OnLogExecuting = (sql, pars) =>
             {
               //我可以在这里面写逻辑
                
               //技巧:AOP中获取IOC对象
               //var serviceBuilder = services.BuildServiceProvider();
               //var log= serviceBuilder.GetService<ILogger<WeatherForecastController>>();
             };
        
        });
  
    return db;
}
var db=GetInstance();
db.Queryable<T>().ToList();//执行操作会进事件

1.2 全部事件

//SQL执行完
db.Aop.OnLogExecuted = (sql, pars) => 
{
 //执行完了可以输出SQL执行时间 (OnLogExecutedDelegate) 
 Console.Write("time:" + db.Ado.SqlExecutionTime.ToString()); 
};
db.Aop.OnLogExecuting = (sql, pars) => //SQL执行前
{
    //获取原生SQL推荐 5.1.4.63  性能OK
    //UtilMethods.GetNativeSql(sql,pars)
    
    //获取无参数化SQL 影响性能只适合调试
    //UtilMethods.GetSqlString(DbType.SqlServer,sql,pars)
};
db.Aop.OnError = (exp) =>//SQL报错
{
    //获取原生SQL推荐 5.1.4.63  性能OK
    //UtilMethods.GetNativeSql(exp.sql,exp.parameters)
    
    //获取无参数SQL对性能有影响,特别大的SQL参数多的,调试使用
    //UtilMethods.GetSqlString(DbType.SqlServer,exp.sql,exp.parameters)           
};
db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
{
    //sql=newsql
    //foreach(var p in pars) //修改
    return new KeyValuePair<string, SugarParameter[]>(sql,pars);
};

2、数据处理:增、删、查 和 改 

(2.1) 插入事件

这个事件可以操作 增(标题2.1) 改(标题2.2) 和 删(标题2.3)

  db.Aop.DataExecuting = (oldValue, entityInfo) =>
  {
   
      /*** 列级别事件:插入的每个列都会进事件 ***/
      if (entityInfo.PropertyName == "CreateTime"&&entityInfo.OperationType== DataFilterType.InsertByObject)
      {
         entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
         
         /*entityInfo有字段所有参数*/
         
         /*oldValue表示当前字段值 等同于下面写法*/
         //var value=entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
         
          /*获取当前列特性*/
          //5.1.3.23 +
          //entityInfo.IsAnyAttribute<特性>()
          //entityInfo.GetAttribute<特性>()
      }
      
      
      /*** 行级别事件 :一条记录只会进一次 ***/   
      if(entityInfo.EntityColumnInfo.IsPrimarykey)
      {
        //entityInfo.EntityValue 拿到单条实体对象
      }
      
      //可以写多个IF
      
   };
 
  /***只能是实体插入***/
  db.Insertable(T).ExecuteReturnIdentity();
  db.Insertable(List<T>).ExecuteReturnIdentity();

(2.2) 更新事件

这个事件可以操作 增(标题2.1) 改(标题2.2) 和 删(标题2.3)

  db.Aop.DataExecuting = (oldValue, entityInfo) =>
  {
  
      /*** 列级别事件 :更新的每一列都会进事件 ***/       
      if (entityInfo.PropertyName =="UpdateTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
      {
         entityInfo.SetValue(DateTime.Now);//修改UpdateTime字段
         
          /*当前列获取特性*/
          //5.1.3.23 +
          //entityInfo.IsAnyAttribute<特性>()
          //entityInfo.GetAttribute<特性>()
      }
      
      
      /*** 行级别事件 :更新一条记录只进一次 ***/   
      if(entityInfo.EntityColumnInfo.IsPrimarykey)
      {
        //entityInfo.EntityValue 拿到单条实体对象
      }

      /***  根据当前列修改另一列 ***/
      //if(当前列逻辑==XXX)
      //var properyDate = entityInfo.EntityValue.GetType().GetProperty("Date");
      //if(properyDate!=null)
      //properyDate.SetValue(entityInfo.EntityValue,1);
      
      
      //可以写多个IF
      

  };
  
  /***实体方式更新***/
  db.Updateable(data).ExecuteCommand(); 
  db.Updateable(dataList).ExecuteCommand(); 
  //指定列更追加过滤器(5.1.4.106-Preview19+) 
    db.Updateable(updateObj).UpdateColumns(it=>new {it.Name},true).ExecuteCommand()
  
  /***表达式方式更新***/
  //新功能 5.1.3.2:表达式更新支持数据过滤器   
  db.Updateable<Order>()
      .SetColumns(it => new Order(){ Name="A" }
      ,appendColumnsByDataFilter:true)//true表式追加过滤器赋值字段
      .Where(it=>it.Id==1).ToSqlString()

(2.3) 删除事件(较高版本

这个事件可以操作 增(标题2.1) 改(标题2.2) 删(标题2.3)

 db.Aop.DataExecuting = (oldValue, entityInfo) =>
 {
      /*** 删除生效 (只有行级事件) ***/       
      if (entityInfo.OperationType == DataFilterType.DeleteByObject)
      {
         //entityInfo.EntityValue 拿到所有实体对象
      }
      
      //可以写多个IF
 }
 
 /***只能是实体删除***/
 db.Deleteable(List<T>).ExecCommand();//实体集合有效
 db.Deleteable(T).ExecCommand();//实体有效

(2.4) 查询事件

新功能:5.1.2-preview05

    //只能是实体查询不能是匿名对象
    db.Aop.DataExecuted = (value, entity) =>
    {
            //只有行级事件
        if (entity.Entity.Type == typeof(Order)) 
        {
            var newValue=entity.GetValue(nameof(Order.Name))+"111";
            entity.SetValue(nameof(Order.Name), newValue);
        }
    };
    //查询出来的值的 name都加上了 111
    List<Order> list2 = db.Queryable<Order>().ToList();

(2.5) 加密和解密

2.1、2.2 和 2.4 一起用就可以实现加密和解密字段   或者 自定义类型实现

(2.6) 查询过滤器

https://www.donet5.com/Home/Doc?typeId=1205

3、差异日志功能(审计)

 3.1 手动配置

注意: 5.0.4.4 支持批量操作

Sql执行完后会进该事件,该事件可以拿到更改前记录和更改后记录,执行时间等参数(可以监控表变动)

该功能和 EF Core ChangeTracker 类似

db.Aop.OnDiffLogEvent = it =>
{
                //操作前记录  包含: 字段描述 列名 值 表名 表描述
                var editBeforeData = it.BeforeData;//插入Before为null,之前还没进库
                //操作后记录   包含: 字段描述 列名 值  表名 表描述
                var editAfterData = it.AfterData;
                var sql = it.Sql;
                var parameter = it.Parameters;
                var data = it.BusinessData;//这边会显示你传进来的对象
                var time = it.Time;
                var  diffType=it.DiffType;//enum insert 、update and delete  
                 
                //Write logic
};
 
 
db.Insertable(new Student() { Name = "beforeName" })
.EnableDiffLogEvent() //注意需要加上启用日志
.ExecuteReturnIdentity();
 
db.Updateable<Student>(new Student()
            {
                Id = id,
                CreateTime = DateTime.Now,
                Name = "afterName",
                SchoolId = 2
            })
.EnableDiffLogEvent(new { title = "update Student", Modular = 1, Operator = "admin" }) //启用并传业务对象参数
.ExecuteCommand();
 
db.Deleteable<Student>(id)
.EnableDiffLogEvent("删除学生") //启用传字符串业务参数
.ExecuteCommand();

只获取差异部分

https://www.donet5.com/ask/9/23905 

3.2 批量配置

升级到:5.1.4.73-preview05

 //程序启动时注册
 StaticConfig.CompleteInsertableFunc=
 StaticConfig.CompleteUpdateableFunc=
 StaticConfig.CompleteDeleteableFunc = it => //it是具体的对象Updateable<T>等是个object
  {
  
       //反射的方法可能多个就需要用GetMethods().Where
        var method = it.GetType().GetMethod("EnableDiffLogEvent");
         method.Invoke(it,new object[] { null });
       
       //技巧:
       //可以定义一个接口只要是这个接口的才走这个逻辑
       //if(db.GetType().GenericTypeArguments[0].GetInterfaces().Any(it=>it==typeof(IDiff))
       //可以根据类型写if
       //if(x.GetType().GenericTypeArguments[0] = typeof(Order)) {   }
  };
  
  //注册完就
  //Insertable、Updateable和Deleteable就会自动调用EnableDiffLogEvent()
  //不需一个一个写

4、实体特性实现AOP

这么改你所有操生成的sql表名前面就加了前缀  select * from "public"."表名"

 return new SqlSugarClient(new ConnectionConfig()
            {
                DbType = SqlSugar.DbType.PostgreSQL,
                ConnectionString = Config.ConnectionString,
                InitKeyType = InitKeyType.Attribute,
                IsAutoCloseConnection = true,
                ConfigureExternalServices=new ConfigureExternalServices() {
                    EntityNameService = (type, entity) => 
                    {
                        entity.DbTableName = "public." + entity.DbTableName;
                    }
                }
            });

上面只修改了表,如果我要表和列都修改呢

EntityNameService = (type, entity) => 
{
  //修改表        
},
EntityService = (type, column) => 
{
   //修改列
}

5、监控所有超过1秒的Sql

   SqlSugarClient db = new SqlSugarClient(new ConnectionConfig()
   {
                DbType = DbType.SqlServer,
                ConnectionString = Config.ConnectionString,
                InitKeyType = InitKeyType.Attribute,
                IsAutoCloseConnection = true
              
   });
    db.Aop.OnLogExecuted = (sql, p) =>
    {
         //执行时间超过1秒
        if (db.Ado.SqlExecutionTime.TotalSeconds > 1) 
        {
            //代码CS文件名
            var fileName= db.Ado.SqlStackTrace.FirstFileName;
            //代码行数
            var fileLine = db.Ado.SqlStackTrace.FirstLine;
            //方法名
            var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
            //db.Ado.SqlStackTrace.MyStackTraceList[1].xxx 获取上层方法的信息
         }
         //相当于EF的 PrintToMiniProfiler
   };

注意:事件不要写错了,写错可能会出现负数,要执行后事件,不能是执行前

6、多租户AOP设置

/***注意***/
//如果你用的 GetConnectionScope或者 GetConnectionScopeWithAttr AOP也应该用 GetConnectionScope 
//如果你用的 GetConnection或者 GetConnectionWithAttr AOP也应该用 GetConnectionScope 
  
  
  SqlSugarClient Db= new SqlSugarClient(new ConnectionConfig(){
           ConnectionString = "连接符字串", 
           DbType = DbType.SqlServer,
           IsAutoCloseConnection = true},
           db=>{  
                    //也可以这里面循环
                   db.GetConnection("1").Aop.OnLogExecuting = (sql, pars) =>
                   {
                     Console.WriteLine("执行1库"+sql);
                   };
                   db.GetConnection("0").Aop.OnLogExecuting = (sql, pars) =>
                   {
                      Console.WriteLine("执行0库"+sql);
                   };
   
  });


7、中间管道

这个功能相当于替换ADO执行方法,已经超出来AOP的范畴

https://www.donet5.com/Home/Doc?typeId=2413

8、查询过滤器

https://www.donet5.com/home/Doc?typeId=1205

9、AOP获取IOC对象

//建一个扩展类
public static class SqlsugarSetup
{
    public static void AddSqlsugarSetup(this IServiceCollection services, IConfiguration configuration, 
    string dbName = "db_master")
    {
        SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
        {
            DbType = SqlSugar.DbType.MySql,
            ConnectionString = configuration.GetConnectionString(dbName),
            IsAutoCloseConnection = true,
        },
            db =>
            {
                //技巧:拿到非ORM注入对象
                var serviceBuilder = services.BuildServiceProvider();
                var log= serviceBuilder.GetService<类>();
            });
        services.AddSingleton<ISqlSugarClient>(sqlSugar);//这边是SqlSugarScope用AddSingleton
    }
}
//Startup.cs文件添加下面代码
services.AddSqlsugarSetup(Configuration);
 
//业务类和仓储对象不能用单例(只有SqlSugarScope注入单例其他不要单例)

10、静态事件

升级到:5.1.4.73-preview05

可以针对Queryable Insertable Updateable Deleteable ISqlSugarClient对象创建完得行全局修改

CompleteQueryableFunc:Queryable创建完事件

CompleteInsertableFunc:  Insertable创建完事件

CompleteUpdateableFunc: Updateable创建完事件

CompleteDeleteableFunc: Deleteable创建完事件

CompleteDbFunc: ISqlSugarClient创建完事件

用例1:

写个用例:

 //程序启动时注册
 StaticConfig.CompleteInsertableFunc= it => //it是具体的对象Insertable<T>等是个object
  {
  
       //反射的方法可能多个就需要用GetMethods().Where
        var method = it.GetType().GetMethod("EnableDiffLogEvent");
         method.Invoke(it,new object[] { null });
       
       //技巧:
       //可以定义一个接口只要是这个接口的才走这个逻辑
       //if(db.GetType().GenericTypeArguments[0].GetInterfaces().Any(it=>it==typeof(IDiff))
  };
  
  //注册完就自动调用.EnableDiffLogEvent()
  db.Insertable(list).Execommand();
  //等同于
  // db.Insertable(list).EnableDiffLogEvent().Execommand();

用例2

使用全局注入AOP 

 //程序启动时注册
StaticConfig.CompleteDbFunc=db=>
{
    db.Aop.xxx=xxx
}


关闭
果糖网