SQLSugar 封装原理详解:从架构到核心模块的底层实现

目录

​编辑

[一、SQLSugar 整体架构设计:轻量 ORM 的分层与组件化](#一、SQLSugar 整体架构设计:轻量 ORM 的分层与组件化)

[1.1 架构分层设计](#1.1 架构分层设计)

[1.1.1 核心组件依赖关系](#1.1.1 核心组件依赖关系)

[1.2 核心类与接口设计](#1.2 核心类与接口设计)

[1.2.1 入口类:SqlSugarClient 与 ISqlSugarClient](#1.2.1 入口类:SqlSugarClient 与 ISqlSugarClient)

[1.2.2 适配接口:IDbAdapter(多数据库核心)](#1.2.2 适配接口:IDbAdapter(多数据库核心))

[二、实体映射机制:ORM 的 "对象 - 关系" 桥梁封装](#二、实体映射机制:ORM 的 “对象 - 关系” 桥梁封装)

[2.1 映射信息的结构封装](#2.1 映射信息的结构封装)

[2.2 映射解析的封装流程](#2.2 映射解析的封装流程)

[2.2.1 步骤 1:反射获取实体类信息](#2.2.1 步骤 1:反射获取实体类信息)

[2.2.2 步骤 2:缓存映射结果](#2.2.2 步骤 2:缓存映射结果)

[2.3 类型映射的适配封装](#2.3 类型映射的适配封装)

[三、SQL 生成器:从 "对象操作" 到 "原生 SQL" 的封装](#三、SQL 生成器:从 “对象操作” 到 “原生 SQL” 的封装)

[3.1 表达式树解析的封装逻辑](#3.1 表达式树解析的封装逻辑)

[3.1.1 核心类:SqlExpressionVisitor](#3.1.1 核心类:SqlExpressionVisitor)

[步骤 1:访问 Lambda 表达式根节点](#步骤 1:访问 Lambda 表达式根节点)

[步骤 2:处理 AndAlso 二元表达式(&&)](#步骤 2:处理 AndAlso 二元表达式(&&))

[步骤 3:处理 GreaterThan 表达式(>)](#步骤 3:处理 GreaterThan 表达式(>))

[步骤 4:处理 Contains 方法调用(u.Name.Contains ("张"))](#步骤 4:处理 Contains 方法调用(u.Name.Contains ("张")))

[3.2 分页 SQL 的适配封装](#3.2 分页 SQL 的适配封装)

[3.2.1 SQL Server 分页实现](#3.2.1 SQL Server 分页实现)

[3.2.2 MySQL 分页实现](#3.2.2 MySQL 分页实现)

[3.3 批量操作的 SQL 优化封装](#3.3 批量操作的 SQL 优化封装)

[3.3.1 批量新增的 SQL 封装](#3.3.1 批量新增的 SQL 封装)

[四、连接管理:从 "创建" 到 "释放" 的生命周期封装](#四、连接管理:从 “创建” 到 “释放” 的生命周期封装)

[4.1 连接池的集成封装](#4.1 连接池的集成封装)

[4.2 连接生命周期的封装](#4.2 连接生命周期的封装)

[4.2.1 自动释放(IsAutoCloseConnection=true,推荐)](#4.2.1 自动释放(IsAutoCloseConnection=true,推荐))

[4.2.2 手动释放(IsAutoCloseConnection=false)](#4.2.2 手动释放(IsAutoCloseConnection=false))

[4.3 多数据库连接的封装](#4.3 多数据库连接的封装)

[五、事务机制:"原子性" 操作的封装实现](#五、事务机制:“原子性” 操作的封装实现)

[5.1 手动事务的封装](#5.1 手动事务的封装)

[5.2 自动事务的封装(基于 AOP 与 IOC)](#5.2 自动事务的封装(基于 AOP 与 IOC))

[5.2.1 步骤 1:注册事务 AOP 服务](#5.2.1 步骤 1:注册事务 AOP 服务)

[5.2.2 步骤 2:事务拦截器实现](#5.2.2 步骤 2:事务拦截器实现)

[5.2.3 步骤 3:使用[Transaction]特性](#5.2.3 步骤 3:使用[Transaction]特性)

[5.3 嵌套事务的封装(基于保存点)](#5.3 嵌套事务的封装(基于保存点))

[六、AOP 模块:"无侵入" 扩展的封装](#六、AOP 模块:“无侵入” 扩展的封装)

[6.1 AOP 的委托链封装](#6.1 AOP 的委托链封装)

[6.2 AOP 的触发流程封装](#6.2 AOP 的触发流程封装)

[6.3 AOP 的扩展场景](#6.3 AOP 的扩展场景)

[6.3.1 SQL 日志记录](#6.3.1 SQL 日志记录)

[6.3.2 性能监控](#6.3.2 性能监控)

[七、缓存机制:"减少数据库查询" 的封装实现](#七、缓存机制:“减少数据库查询” 的封装实现)

[7.1 缓存 key 的生成封装](#7.1 缓存 key 的生成封装)

[7.2 内存缓存的封装](#7.2 内存缓存的封装)

[7.3 分布式缓存的封装(以 Redis 为例)](#7.3 分布式缓存的封装(以 Redis 为例))

[7.4 缓存同步的封装(数据更新触发缓存清除)](#7.4 缓存同步的封装(数据更新触发缓存清除))

[八、代码生成器:"数据库结构→代码" 的自动化封装](#八、代码生成器:“数据库结构→代码” 的自动化封装)

[8.1 数据库元数据解析的封装](#8.1 数据库元数据解析的封装)

[8.2 实体类代码生成的封装](#8.2 实体类代码生成的封装)

[8.3 仓储类与服务类生成的封装](#8.3 仓储类与服务类生成的封装)

[九、总结:SQLSugar 封装的设计思想与优势](#九、总结:SQLSugar 封装的设计思想与优势)


一、SQLSugar 整体架构设计:轻量 ORM 的分层与组件化

SQLSugar 作为.NET 生态下的轻量级 ORM,其封装核心遵循 "分层解耦、组件化复用" 设计原则,整体架构分为基础设施层、核心功能层、应用适配层三层,各层通过接口定义边界,既保证核心逻辑的独立性,又支持灵活扩展。

1.1 架构分层设计

|-------|------------------------------------------|-----------------------------------------------|
| 层级 | 核心职责 | 关键组件 / 接口 |
| 基础设施层 | 提供底层依赖能力(连接管理、反射缓存、序列化),为上层提供基础支撑 | IDbConnection、ICacheService、反射工具类 |
| 核心功能层 | 实现 ORM 核心逻辑(实体映射、SQL 生成、事务、AOP),是封装的核心载体 | SqlSugarClient、ISqlSugarClient、SQL 生成器、AOP 模块 |
| 应用适配层 | 适配不同应用场景(IOC 集成、多数据库、代码生成),降低上层使用成本 | IocConfig、IDbAdapter、CodeGenerator |

1.1.1 核心组件依赖关系

SQLSugar 的核心组件通过 "依赖注入 + 接口解耦" 组织,关键依赖链如下:

复制代码

SqlSugarClient(入口)

├─ 依赖 ConnectionConfig(配置)

├─ 依赖 IDbAdapter(数据库适配)

├─ 依赖 ICacheService(缓存)

├─ 依赖 AOP委托链(日志、错误处理)

└─ 依赖 实体映射缓存(反射结果)

这种设计确保各组件可独立替换(如替换缓存为 Redis、新增数据库适配),符合 "开闭原则"。

1.2 核心类与接口设计

1.2.1 入口类:SqlSugarClient 与 ISqlSugarClient

SQLSugar 通过ISqlSugarClient接口定义核心操作契约(CRUD、事务、查询构建),SqlSugarClient作为实现类,是用户交互的唯一入口。其封装关键点:

  • 构造函数封装:接收ConnectionConfig或List<ConnectionConfig>,内部初始化数据库适配器、连接池、映射缓存;
  • 接口隔离:将复杂操作拆解为子接口(如IQueryable<T>负责查询、IInsertable<T>负责新增),避免类膨胀;
  • 单例与多实例控制:非 IOC 场景下支持手动创建实例,IOC 场景下通过Scoped生命周期避免线程安全问题。
1.2.2 适配接口:IDbAdapter(多数据库核心)

为支持多数据库,SQLSugar 封装了IDbAdapter接口,定义数据库专属语法的适配契约,不同数据库通过实现该接口完成差异化适配。接口核心方法:

复制代码

public interface IDbAdapter

{

// 生成分页SQL

string GetPageSql(string sql, long skip, long take, string orderBy);

// 获取当前时间函数(如GETDATE()、NOW())

string GetDateFunc();

// 处理参数前缀(如@、:)

string GetParamPrefix();

// 字段类型映射(数据库类型→C#类型)

Type GetCSharpType(string dbType);

}

例如:

  • SqlServerAdapter实现GetPageSql生成ROW_NUMBER()分页;
  • MySqlAdapter实现GetPageSql生成LIMIT分页;
  • OracleAdapter实现GetParamPrefix返回:(Oracle 参数前缀)。

SqlSugarClient 初始化时,根据ConnectionConfig.DbType自动选择对应的IDbAdapter实例,确保 SQL 语法适配。

二、实体映射机制:ORM 的 "对象 - 关系" 桥梁封装

实体映射是 ORM 的核心,SQLSugar 通过 "反射解析 + 缓存优化" 封装实体类与数据库表的映射关系,避免重复反射开销,同时支持特性与约定两种映射方式。

2.1 映射信息的结构封装

SQLSugar 将实体映射信息封装为EntityInfo和ColumnInfo类,分别对应 "表" 和 "字段" 的映射元数据:

复制代码

// 表映射元数据

public class EntityInfo

{

public Type EntityType { get; set; } // 实体类类型

public string TableName { get; set; } // 数据库表名

public List<ColumnInfo> Columns { get; set; } // 字段列表

public ColumnInfo PrimaryKey { get; set; } // 主键字段

}

// 字段映射元数据

public class ColumnInfo

{

public string ColumnName { get; set; } // 数据库字段名

public string PropertyName { get; set; } // 实体类属性名

public Type PropertyType { get; set; } // 属性类型

public bool IsPrimaryKey { get; set; } // 是否主键

public bool IsIdentity { get; set; } // 是否自增

public bool IsNullable { get; set; } // 是否允许为NULL

public int Length { get; set; } // 字段长度(字符串)

}

这些元数据是后续 SQL 生成、参数绑定的基础。

2.2 映射解析的封装流程

SQLSugar 在首次使用实体类(如Queryable<User>())时,触发映射解析,流程如下:

2.2.1 步骤 1:反射获取实体类信息

通过Type.GetTypeInfo()反射实体类,获取类特性([SugarTable])和属性信息:

复制代码

// 简化代码:解析表名

var sugarTableAttr = entityType.GetCustomAttribute<SugarTableAttribute>();

var tableName = sugarTableAttr?.TableName ?? entityType.Name; // 约定:类名→表名

// 解析属性

foreach (var prop in entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance))

{

var columnInfo = new ColumnInfo

{

PropertyName = prop.Name,

PropertyType = prop.PropertyType

};

// 解析字段特性([SugarColumn])

var sugarColumnAttr = prop.GetCustomAttribute<SugarColumnAttribute>();

if (sugarColumnAttr != null)

{

columnInfo.ColumnName = sugarColumnAttr.ColumnName ?? prop.Name;

columnInfo.IsPrimaryKey = sugarColumnAttr.IsPrimaryKey;

columnInfo.IsIdentity = sugarColumnAttr.IsIdentity;

columnInfo.IsNullable = sugarColumnAttr.IsNullable;

columnInfo.Length = sugarColumnAttr.Length;

}

// 约定:若未指定主键,默认"Id"或"XXXId"为PK

if (!columnInfo.IsPrimaryKey &&

(prop.Name.Equals("Id", StringComparison.OrdinalIgnoreCase) ||

prop.Name.EndsWith("Id", StringComparison.OrdinalIgnoreCase)))

{

columnInfo.IsPrimaryKey = true;

// 约定:整数类型主键默认自增

if (prop.PropertyType.IsValueType && !prop.PropertyType.IsNullableType())

{

columnInfo.IsIdentity = true;

}

}

entityInfo.Columns.Add(columnInfo);

}

2.2.2 步骤 2:缓存映射结果

为避免重复反射(反射性能开销较大),SQLSugar 将解析后的EntityInfo缓存到静态字典ConcurrentDictionary<Type, EntityInfo>中,key 为实体类Type,后续使用该实体类时直接从缓存获取:

复制代码

private static readonly ConcurrentDictionary<Type, EntityInfo> _entityCache = new();

public EntityInfo GetEntityInfo(Type entityType)

{

if (_entityCache.TryGetValue(entityType, out var entityInfo))

{

return entityInfo;

}

// 解析映射信息

entityInfo = ParseEntityInfo(entityType);

// 缓存到字典

_entityCache.TryAdd(entityType, entityInfo);

return entityInfo;

}

这一封装使映射解析的性能开销仅发生一次,显著提升后续操作效率。

2.3 类型映射的适配封装

不同数据库的字段类型与 C# 类型存在差异(如 SQL Server 的nvarchar对应 C# 的string,MySQL 的int对应 C# 的int),SQLSugar 通过IDbAdapter封装类型映射逻辑:

复制代码

// MySqlAdapter类型映射实现

public Type GetCSharpType(string dbType)

{

return dbType.ToLower() switch

{

"varchar" or "char" or "text" => typeof(string),

"int" or "tinyint" => typeof(int),

"bigint" => typeof(long),

"decimal" => typeof(decimal),

"datetime" => typeof(DateTime),

_ => typeof(object)

};

}

在代码生成器或反向映射(数据库表→实体类)时,通过该方法自动匹配 C# 类型,避免手动转换错误。

三、SQL 生成器:从 "对象操作" 到 "原生 SQL" 的封装

SQL 生成是 SQLSugar 的核心能力之一,其通过 "表达式树解析 + 数据库适配",将 Lambda 表达式(如u => u.Age > 20)转化为适配目标数据库的参数化 SQL,同时避免 SQL 注入。

3.1 表达式树解析的封装逻辑

SQLSugar 的Queryable<T>、Updateable<T>等操作类,底层依赖ExpressionVisitor(表达式访问器)解析 Lambda 表达式,将其转化为 SQL 条件、字段列表等片段。

3.1.1 核心类:SqlExpressionVisitor

SqlExpressionVisitor继承自ExpressionVisitor,重写表达式树节点的访问方法(如VisitBinary处理二元表达式、VisitMember处理成员访问),逐步构建 SQL 片段和参数列表。

以解析u => u.Age > 20 && u.Name.Contains("张")为例,解析流程如下:

步骤 1:访问 Lambda 表达式根节点
复制代码

public override Expression VisitLambda<T>(Expression<T> node)

{

// 访问Lambda体(此处为BinaryExpression:AndAlso)

return Visit(node.Body);

}

步骤 2:处理 AndAlso 二元表达式(&&)
复制代码

protected override Expression VisitBinary(BinaryExpression node)

{

if (node.NodeType == ExpressionType.AndAlso)

{

// 递归访问左节点(u.Age > 20)和右节点(u.Name.Contains("张"))

var leftSql = Visit(node.Left);

var rightSql = Visit(node.Right);

// 拼接SQL条件:左条件 AND 右条件

_sqlBuilder.Append($"({leftSql} AND {rightSql})");

}

// 其他二元表达式(如GreaterThan、Equal)处理逻辑...

return node;

}

步骤 3:处理 GreaterThan 表达式(>)
复制代码

protected override Expression VisitBinary(BinaryExpression node)

{

if (node.NodeType == ExpressionType.GreaterThan)

{

// 访问左操作数(u.Age,成员访问)

var left = Visit(node.Left) as string;

// 访问右操作数(20,常量)

var right = Visit(node.Right) as object;

// 生成参数(避免SQL注入)

var paramName = $"@{_paramCounter++}";

_parameters.Add(new SugarParameter(paramName, right));

// 拼接条件:[Age] > @0

_sqlBuilder.Append($"{left} > {paramName}");

}

// ...

return node;

}

步骤 4:处理 Contains 方法调用(u.Name.Contains ("张"))
复制代码

protected override Expression VisitMethodCall(MethodCallExpression node)

{

if (node.Method.Name == nameof(string.Contains))

{

// 访问实例(u.Name)

var member = Visit(node.Object) as string;

// 访问参数("张")

var value = Visit(node.Arguments[0]) as object;

// 生成参数:@1 = '%张%'

var paramName = $"@{_paramCounter++}";

_parameters.Add(new SugarParameter(paramName, $"%{value}%"));

// 拼接Like条件:[Name] LIKE @1

_sqlBuilder.Append($"{member} LIKE {paramName}");

}

// ...

return node;

}

最终生成的 SQL 条件的是:([Age] > @0 AND [Name] LIKE @1),参数列表为@0=20、@1='%张%',既完成了表达式解析,又通过参数化避免了 SQL 注入。

3.2 分页 SQL 的适配封装

不同数据库的分页语法差异较大(如 SQL Server 用ROW_NUMBER(),MySQL 用LIMIT),SQLSugar 通过IDbAdapter.GetPageSql封装分页逻辑,实现 "一套代码,多库适配"。

3.2.1 SQL Server 分页实现
复制代码

public string GetPageSql(string sql, long skip, long take, string orderBy)

{

// 原始SQL:SELECT * FROM Users WHERE Age > 20

// 生成ROW_NUMBER()子查询

var rowSql = $"SELECT *, ROW_NUMBER() OVER (ORDER BY {orderBy}) AS RowNum FROM ({sql}) AS Temp";

// 外层筛选分页范围

var pageSql = $"SELECT * FROM ({rowSql}) AS Temp WHERE RowNum > {skip} AND RowNum <= {skip + take}";

return pageSql;

}

3.2.2 MySQL 分页实现
复制代码

public string GetPageSql(string sql, long skip, long take, string orderBy)

{

// 直接拼接LIMIT:SELECT * FROM Users WHERE Age > 20 ORDER BY RegisterTime DESC LIMIT 10,10

return $"{sql} ORDER BY {orderBy} LIMIT {skip},{take}";

}

在ToPageList方法中,SQLSugar 先生成基础 SQL,再调用当前IDbAdapter的GetPageSql生成适配分页 SQL,无需用户关注数据库差异。

3.3 批量操作的 SQL 优化封装

批量新增、修改等操作若循环执行单条 SQL,会产生大量数据库交互开销。SQLSugar 通过 "批量 SQL 拼接 + 参数复用" 封装优化,减少交互次数。

3.3.1 批量新增的 SQL 封装

以 SQL Server 为例,批量新增时生成VALUES多行 SQL:

复制代码

// 批量新增用户列表

public string GenerateBatchInsertSql(List<User> users, EntityInfo entityInfo)

{

var columns = string.Join(",", entityInfo.Columns.Where(c => !c.IsIdentity).Select(c => $"[{c.ColumnName}]"));

var paramBuilder = new StringBuilder();

var valueBuilder = new StringBuilder();

int paramIndex = 0;

foreach (var user in users)

{

var values = new List<string>();

foreach (var column in entityInfo.Columns.Where(c => !c.IsIdentity))

{

var prop = entityInfo.EntityType.GetProperty(column.PropertyName);

var value = prop.GetValue(user);

var paramName = $"@{paramIndex++}";

paramBuilder.Append($"{paramName}={value},");

values.Add(paramName);

}

valueBuilder.Append($"({string.Join(",", values)}),");

}

// 生成SQL:INSERT INTO Users ([UserName],[Age],[Gender]) VALUES (@0,@1,@2),(@3,@4,@5)

var sql = $"INSERT INTO [{entityInfo.TableName}] ({columns}) VALUES {valueBuilder.ToString().TrimEnd(',')}";

return sql;

}

这种封装将 N 条新增操作转化为 1 条 SQL,数据库交互次数从 N 次减少到 1 次,显著提升性能。

四、连接管理:从 "创建" 到 "释放" 的生命周期封装

SQLSugar 基于ADO.NET连接池,封装了连接的 "获取 - 使用 - 释放" 生命周期,避免连接泄露,同时支持多数据库连接管理。

4.1 连接池的集成封装

SQLSugar 默认使用ADO.NET内置连接池(无需额外配置),连接池的核心参数(如最大连接数、连接超时)通过连接字符串配置:

复制代码

// SQL Server连接字符串(配置连接池)

Server=localhost;Database=TestDB;Uid=sa;Pwd=123456;Max Pool Size=100;Connect Timeout=30;

  • Max Pool Size:连接池最大连接数(默认 100);
  • Connect Timeout:连接超时时间(默认 15 秒)。

SQLSugar 不重复造轮子,而是基于原生连接池优化连接获取逻辑,确保连接复用率。

4.2 连接生命周期的封装

SQLSugar 通过IsAutoCloseConnection配置,封装连接的自动释放逻辑,生命周期如下:

4.2.1 自动释放(IsAutoCloseConnection=true,推荐)
复制代码

// 简化代码:执行查询时的连接管理

public List<T> Query<T>(string sql, List<SugarParameter> pars)

{

IDbConnection conn = null;

try

{

// 1. 从连接池获取连接

conn = GetConnection();

// 2. 执行SQL

var result = conn.Query<T>(sql, pars).ToList();

return result;

}

catch (Exception ex)

{

// 触发AOP错误事件

_aop.OnError?.Invoke(sql, pars, ex);

throw;

}

finally

{

// 3. 自动释放连接(归还到连接池,非关闭物理连接)

if (IsAutoCloseConnection && conn != null && conn.State == ConnectionState.Open)

{

conn.Close();

}

}

}

关键:conn.Close()并非关闭物理连接,而是将连接归还到连接池,供后续操作复用,减少连接创建的开销。

4.2.2 手动释放(IsAutoCloseConnection=false)

适用于长事务场景,需用户手动调用Close()或Dispose()释放连接:

复制代码

// 手动管理连接

using (var conn = db.Ado.GetConnection())

{

conn.Open();

// 执行多步操作(事务)

db.Ado.BeginTran();

// ...

db.Ado.CommitTran();

// using结束后自动Dispose,释放连接

}

通过using语句确保连接最终释放,避免泄露。

4.3 多数据库连接的封装

当项目需操作多个数据库时,SQLSugar 通过ConfigId标识不同连接配置,封装多连接管理:

复制代码

// 内部维护多连接配置字典

private readonly ConcurrentDictionary<string, ConnectionConfig> _connectionConfigs = new();

// 添加多连接配置

public void AddConnectionConfig(ConnectionConfig config)

{

_connectionConfigs.TryAdd(config.ConfigId, config);

}

// 根据ConfigId获取连接

public IDbConnection GetConnection(string configId = "MainDB")

{

if (!_connectionConfigs.TryGetValue(configId, out var config))

{

throw new Exception($"未找到ConfigId为{configId}的连接配置");

}

// 根据数据库类型创建连接实例

IDbConnection conn = config.DbType switch

{

DbType.SqlServer => new SqlConnection(config.ConnectionString),

DbType.MySql => new MySqlConnection(config.ConnectionString),

DbType.Oracle => new OracleConnection(config.ConnectionString),

_ => throw new Exception("不支持的数据库类型")

};

return conn;

}

用户通过db.GetConnection("MySqlDB")即可切换到指定数据库,无需重新创建SqlSugarClient实例。

五、事务机制:"原子性" 操作的封装实现

SQLSugar 封装了手动事务与自动事务两种模式,确保一组数据库操作的原子性(要么全成功,要么全回滚),同时支持嵌套事务。

5.1 手动事务的封装

手动事务基于ADO.NET的IDbTransaction,封装事务的 "开启 - 提交 - 回滚" 流程:

复制代码

// 内部维护当前事务

private IDbTransaction _currentTransaction;

// 开启事务

public void BeginTran()

{

var conn = GetConnection();

if (conn.State != ConnectionState.Open)

{

conn.Open();

}

// 创建事务实例

_currentTransaction = conn.BeginTransaction();

// 绑定事务到后续操作(确保后续SQL使用同一事务)

_isInTransaction = true;

}

// 提交事务

public void CommitTran()

{

if (_currentTransaction != null)

{

_currentTransaction.Commit();

// 释放事务和连接

_currentTransaction.Dispose();

_currentTransaction = null;

_isInTransaction = false;

if (IsAutoCloseConnection)

{

GetConnection().Close();

}

}

}

// 回滚事务

public void RollbackTran()

{

if (_currentTransaction != null)

{

_currentTransaction.Rollback();

_currentTransaction.Dispose();

_currentTransaction = null;

_isInTransaction = false;

if (IsAutoCloseConnection)

{

GetConnection().Close();

}

}

}

关键:开启事务后,后续所有 SQL 操作会自动绑定到_currentTransaction,确保在同一事务上下文执行。

5.2 自动事务的封装(基于 AOP 与 IOC)

ASP.NET Core 等 IOC 场景下,SQLSugar 通过[Transaction]特性 + AOP 拦截,实现事务自动管理,封装流程如下:

5.2.1 步骤 1:注册事务 AOP 服务
复制代码

// Program.cs中注册事务拦截器

builder.Services.AddScoped<TransactionInterceptor>();

// 配置AOP拦截控制器方法

builder.Services.AddControllers(options =>

{

options.Filters.Add<TransactionInterceptor>();

});

5.2.2 步骤 2:事务拦截器实现
复制代码

public class TransactionInterceptor : IActionFilter

{

private readonly ISqlSugarClient _db;

public TransactionInterceptor(ISqlSugarClient db)

{

_db = db;

}

// 方法执行前开启事务

public void OnActionExecuting(ActionExecutingContext context)

{

// 判断方法是否标记[Transaction]特性

var hasTransactionAttr = context.ActionDescriptor.EndpointMetadata

.Any(em => em is TransactionAttribute);

if (hasTransactionAttr)

{

_db.Ado.BeginTran();

// 将事务状态存入上下文,供执行后使用

context.HttpContext.Items["IsInTransaction"] = true;

}

}

// 方法执行后提交/回滚

public void OnActionExecuted(ActionExecutedContext context)

{

if (context.HttpContext.Items.TryGetValue("IsInTransaction", out var isInTransaction) &&

(bool)isInTransaction)

{

if (context.Exception == null)

{

// 无异常,提交事务

_db.Ado.CommitTran();

}

else

{

// 有异常,回滚事务

_db.Ado.RollbackTran();

}

}

}

}

5.2.3 步骤 3:使用[Transaction]特性
复制代码

[HttpPost("AddUserAndOrder")]

[Transaction] // 自动开启事务

public IActionResult AddUserAndOrder(User user, Order order)

{

_db.Insertable(user).ExecuteReturnIdentity();

_db.Insertable(order).ExecuteCommand();

return Ok();

}

这种封装使开发者无需手动编写事务代码,只需添加特性即可确保方法内所有操作的原子性。

5.3 嵌套事务的封装(基于保存点)

SQLSugar 通过数据库 "保存点(Savepoint)" 封装嵌套事务,支持内层事务独立回滚,而不影响外层事务:

复制代码

// 开启嵌套事务(创建保存点)

public void BeginNestedTran(string savepointName)

{

if (_currentTransaction == null)

{

throw new Exception("需先开启外层事务");

}

// 创建保存点

_currentTransaction.Save(savepointName);

}

// 回滚嵌套事务(回滚到保存点)

public void RollbackNestedTran(string savepointName)

{

if (_currentTransaction != null)

{

_currentTransaction.Rollback(savepointName);

}

}

示例:

复制代码

db.Ado.BeginTran(); // 外层事务

try

{

db.Insertable(user1).ExecuteCommand(); // 操作1

db.Ado.BeginNestedTran("Savepoint1"); // 开启嵌套事务

try

{

db.Insertable(user2).ExecuteCommand(); // 操作2

throw new Exception("嵌套事务出错");

db.Ado.CommitTran(); // 不会执行

}

catch

{

db.Ado.RollbackNestedTran("Savepoint1"); // 仅回滚操作2

}

db.Ado.CommitTran(); // 提交操作1

}

catch

{

db.Ado.RollbackTran(); // 回滚操作1

}

最终仅user1插入成功,user2被回滚,实现嵌套事务的灵活控制。

六、AOP 模块:"无侵入" 扩展的封装

SQLSugar 通过 AOP(面向切面编程)封装 SQL 执行的拦截能力,支持日志记录、性能监控、错误处理等扩展,且不侵入核心业务代码。

6.1 AOP 的委托链封装

SQLSugar 的 AOP 基于 "委托链" 实现,定义了 SQL 执行各阶段的委托事件:

复制代码

public class AopConfig

{

// SQL执行前触发(可修改SQL和参数)

public Action<string, List<SugarParameter>> OnLogExecuting { get; set; }

// SQL执行后触发(可获取执行时间、影响行数)

public Action<string, List<SugarParameter>, long> OnLogExecuted { get; set; }

// SQL执行出错时触发

public Action<string, List<SugarParameter>, Exception> OnError { get; set; }

}

这些委托通过 "多播委托" 支持多个方法注册,例如同时注册日志记录和性能监控:

复制代码

db.Aop.OnLogExecuting += (sql, pars) => Console.WriteLine(sql); // 日志

db.Aop.OnLogExecuting += (sql, pars) => _performanceMonitor.Start(sql); // 性能监控

6.2 AOP 的触发流程封装

在 SQL 执行的关键节点,SQLSugar 触发对应的 AOP 委托,流程如下:

复制代码

public int ExecuteNonQuery(string sql, List<SugarParameter> pars)

{

long startTime = DateTime.Now.Ticks;

try

{

// 1. 执行前触发OnLogExecuting

_aop.OnLogExecuting?.Invoke(sql, pars);

var conn = GetConnection();

var cmd = CreateCommand(conn, sql, pars);

int rows = cmd.ExecuteNonQuery();

// 2. 执行后触发OnLogExecuted(计算执行时间)

long executeTime = (DateTime.Now.Ticks - startTime) / 10000; // 毫秒

_aop.OnLogExecuted?.Invoke(sql, pars, executeTime);

return rows;

}

catch (Exception ex)

{

// 3. 出错时触发OnError

_aop.OnError?.Invoke(sql, pars, ex);

throw;

}

}

这种封装确保 AOP 逻辑与核心 SQL 执行逻辑解耦,开发者可按需扩展,无需修改 SQLSugar 源码。

6.3 AOP 的扩展场景

6.3.1 SQL 日志记录
复制代码

db.Aop.OnLogExecuting = (sql, pars) =>

{

// 记录SQL到日志文件

LogHelper.Info($"SQL: {sql}");

LogHelper.Info($"Params: {string.Join(",", pars.Select(p => $"{p.ParameterName}={p.Value}"))}");

};

6.3.2 性能监控
复制代码

db.Aop.OnLogExecuting += (sql, pars) =>

{

// 记录SQL开始执行时间

HttpContext.Current.Items[$"SqlStartTime_{sql.GetHashCode()}"] = DateTime.Now;

};

db.Aop.OnLogExecuted += (sql, pars, executeTime) =>

{

// 输出执行时间(毫秒)

Console.WriteLine($"SQL执行时间:{executeTime}ms");

// 若执行时间过长,告警

if (executeTime > 500)

{

LogHelper.Warn($"SQL执行超时:{sql},耗时:{executeTime}ms");

}

};

七、缓存机制:"减少数据库查询" 的封装实现

SQLSugar 封装了多级缓存能力(内存缓存、分布式缓存如 Redis),通过 "缓存 key 生成 + 过期策略 + 缓存同步",减少重复数据库查询,提升性能。

7.1 缓存 key 的生成封装

为避免缓存冲突,SQLSugar 根据 "查询标识 + 参数" 生成唯一缓存 key,核心逻辑:

复制代码

public string GenerateCacheKey(string sql, List<SugarParameter> pars)

{

// 1. 拼接SQL和参数(参数按顺序排序,确保相同参数生成相同字符串)

var paramStr = string.Join(",", pars.OrderBy(p => p.ParameterName)

.Select(p => $"{p.ParameterName}={p.Value ?? "null"}"));

// 2. 计算MD5哈希(避免key过长)

using var md5 = MD5.Create();

var bytes = Encoding.UTF8.GetBytes($"{sql}_{paramStr}");

var hash = md5.ComputeHash(bytes);

// 3. 转化为16进制字符串作为key

return BitConverter.ToString(hash).Replace("-", "").ToLower();

}

例如:

  • SQL:SELECT * FROM Users WHERE Age > @0;
  • 参数:@0=20;
  • 生成 key:a3f2d4e5b6c7a8b9c0d1e2f3a4b5c6d7。

7.2 内存缓存的封装

SQLSugar 默认使用内存缓存(基于ConcurrentDictionary),封装缓存的 "添加 - 获取 - 删除" 操作:

复制代码

public class MemoryCacheService : ICacheService

{

// 缓存字典(key:缓存key,value:缓存项)

private readonly ConcurrentDictionary<string, CacheItem> _cache = new();

// 过期清理定时器(定期清理过期缓存)

private readonly Timer _expireTimer;

public MemoryCacheService()

{

// 每30秒清理一次过期缓存

_expireTimer = new Timer(RemoveExpiredCache, null, 0, 30000);

}

// 添加缓存(支持绝对过期)

public void Add<V>(string key, V value, int expireSeconds)

{

var cacheItem = new CacheItem

{

Value = value,

ExpireTime = DateTime.Now.AddSeconds(expireSeconds)

};

_cache.AddOrUpdate(key, cacheItem, (k, old) => cacheItem);

}

// 获取缓存

public V Get<V>(string key)

{

if (_cache.TryGetValue(key, out var cacheItem))

{

// 检查是否过期

if (cacheItem.ExpireTime > DateTime.Now)

{

return (V)cacheItem.Value;

}

// 过期则删除

_cache.TryRemove(key, out _);

}

return default;

}

// 删除缓存

public void Remove(string key)

{

_cache.TryRemove(key, out _);

}

// 清理过期缓存

private void RemoveExpiredCache(object state)

{

var expiredKeys = _cache.Where(kv => kv.Value.ExpireTime <= DateTime.Now)

.Select(kv => kv.Key)

.ToList();

foreach (var key in expiredKeys)

{

_cache.TryRemove(key, out _);

}

}

// 缓存项类(包含值和过期时间)

private class CacheItem

{

public object Value { get; set; }

public DateTime ExpireTime { get; set; }

}

}

7.3 分布式缓存的封装(以 Redis 为例)

通过ICacheService接口解耦,SQLSugar 支持自定义分布式缓存(如 Redis),封装逻辑:

复制代码

public class RedisCacheService : ICacheService

{

private readonly IDistributedCache _redisCache;

private readonly JsonSerializerOptions _jsonOptions;

public RedisCacheService(IDistributedCache redisCache)

{

_redisCache = redisCache;

_jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };

}

// 添加缓存

public void Add<V>(string key, V value, int expireSeconds)

{

// 序列化(JSON)

var json = JsonSerializer.Serialize(value, _jsonOptions);

// 设置Redis缓存(绝对过期)

_redisCache.SetString(key, json, new DistributedCacheEntryOptions

{

AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(expireSeconds)

});

}

// 获取缓存

public V Get<V>(string key)

{

var json = _redisCache.GetString(key);

if (string.IsNullOrEmpty(json))

{

return default;

}

// 反序列化

return JsonSerializer.Deserialize<V>(json, _jsonOptions);

}

// 删除缓存

public void Remove(string key)

{

_redisCache.Remove(key);

}

}

在 IOC 中注册 Redis 缓存:

复制代码

// Program.cs

builder.Services.AddStackExchangeRedisCache(options =>

{

options.Configuration = "localhost:6379"; // Redis连接字符串

});

// 注入Redis缓存服务

builder.Services.AddScoped<ICacheService, RedisCacheService>();

// SQLSugar使用Redis缓存

builder.Services.AddSqlSugar(options =>

{

options.ConnectionConfigs = new List<ConnectionConfig> { /* 连接配置 */ };

options.CacheService = new RedisCacheService(builder.Services.BuildServiceProvider().GetRequiredService<IDistributedCache>());

});

7.4 缓存同步的封装(数据更新触发缓存清除)

当数据发生修改(Update/Delete/Insert)时,SQLSugar 自动清除相关缓存,避免缓存脏数据。核心逻辑:

复制代码

// 执行Update操作后,清除相关缓存

public int Update<T>(T entity)

{

var entityInfo = GetEntityInfo(typeof(T));

var rows = ExecuteNonQuery(GenerateUpdateSql(entity), GenerateUpdateParams(entity));

// 清除该表的所有缓存(简化逻辑,实际可更精细)

var tableCacheKeys = _cacheService.GetAllKeys().Where(key => key.Contains(entityInfo.TableName)).ToList();

foreach (var key in tableCacheKeys)

{

_cacheService.Remove(key);

}

return rows;

}

更精细的缓存同步可通过 AOP 实现,例如根据修改的主键值,清除包含该主键的查询缓存。

八、代码生成器:"数据库结构→代码" 的自动化封装

SQLSugar 代码生成器通过 "数据库元数据解析 + 代码模板拼接",自动生成实体类、仓储类、服务类,减少重复编码。

8.1 数据库元数据解析的封装

代码生成器首先连接数据库,通过 "信息_schema" 视图查询表、字段、主键等元数据:

复制代码

// 查询表列表(SQL Server)

public List<TableInfo> GetTables(string connectionString)

{

using var conn = new SqlConnection(connectionString);

var sql = @"

SELECT

TABLE_NAME AS TableName,

TABLE_COMMENT AS TableDescription

FROM INFORMATION_SCHEMA.TABLES

WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA = 'dbo'

";

return conn.Query<TableInfo>(sql).ToList();

}

// 查询字段列表(SQL Server)

public List<ColumnInfo> GetColumns(string connectionString, string tableName)

{

using var conn = new SqlConnection(connectionString);

var sql = @"

SELECT

COLUMN_NAME AS ColumnName,

DATA_TYPE AS DbType,

IS_NULLABLE AS IsNullable,

CHARACTER_MAXIMUM_LENGTH AS Length,

COLUMN_COMMENT AS ColumnDescription,

COLUMN_DEFAULT AS DefaultValue

FROM INFORMATION_SCHEMA.COLUMNS

WHERE TABLE_NAME = @TableName AND TABLE_SCHEMA = 'dbo'

";

return conn.Query<ColumnInfo>(sql, new { TableName = tableName }).ToList();

}

// 查询主键(SQL Server)

public string GetPrimaryKey(string connectionString, string tableName)

{

using var conn = new SqlConnection(connectionString);

var sql = @"

SELECT COLUMN_NAME

FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE

WHERE OBJECTPROPERTY(OBJECT_ID(CONSTRAINT_SCHEMA + '.' + CONSTRAINT_NAME), 'IsPrimaryKey') = 1

AND TABLE_NAME = @TableName AND TABLE_SCHEMA = 'dbo'

";

return conn.QueryFirstOrDefault<string>(sql, new { TableName = tableName });

}

8.2 实体类代码生成的封装

根据解析的元数据,通过字符串拼接生成实体类代码,处理特性、注释等:

复制代码

public string GenerateEntityCode(TableInfo table, List<ColumnInfo> columns, string primaryKey)

{

var codeBuilder = new StringBuilder();

// 命名空间

codeBuilder.AppendLine("using System;");

codeBuilder.AppendLine("using SqlSugar;");

codeBuilder.AppendLine();

// 表特性(包含描述)

codeBuilder.AppendLine($"[SugarTable(\"{table.TableName}\", \"{table.TableDescription}\")]");

codeBuilder.AppendLine($"public class {table.TableName}");

codeBuilder.AppendLine("{");

// 生成字段

foreach (var column in columns)

{

// 字段注释

if (!string.IsNullOrEmpty(column.ColumnDescription))

{

codeBuilder.AppendLine($" /// <summary>");

codeBuilder.AppendLine($" /// {column.ColumnDescription}");

codeBuilder.AppendLine($" /// </summary>");

}

// 字段特性

var attrBuilder = new StringBuilder();

attrBuilder.Append("[SugarColumn(");

// 主键特性

if (column.ColumnName == primaryKey)

{

attrBuilder.Append("IsPrimaryKey = true, ");

// 自增(整数类型默认自增)

if (IsIntType(column.DbType))

{

attrBuilder.Append("IsIdentity = true, ");

}

}

// 字段名(若与属性名一致,可省略)

if (column.ColumnName != column.ColumnName) // 实际逻辑:判断是否需要显式指定ColumnName

{

attrBuilder.Append($"ColumnName = \"{column.ColumnName}\", ");

}

// 可空特性

attrBuilder.Append($"IsNullable = {column.IsNullable.Equals("YES", StringComparison.OrdinalIgnoreCase).ToString().ToLower()}, ");

// 长度(字符串类型)

if (IsStringType(column.DbType) && column.Length.HasValue)

{

attrBuilder.Append($"Length = {column.Length.Value}, ");

}

// 默认值

if (!string.IsNullOrEmpty(column.DefaultValue))

{

attrBuilder.Append($"DefaultValue = \"{column.DefaultValue}\", ");

}

// 移除末尾逗号

if (attrBuilder.ToString().EndsWith(", "))

{

attrBuilder.Length -= 2;

}

attrBuilder.Append(")]");

codeBuilder.AppendLine($" {attrBuilder}");

// 字段类型(C#类型)

var csharpType = GetCSharpType(column.DbType, column.IsNullable);

// 字段定义

codeBuilder.AppendLine($" public {csharpType} {column.ColumnName} {``{ get; set; }}");

codeBuilder.AppendLine();

}

codeBuilder.AppendLine("}");

return codeBuilder.ToString();

}

// 判断是否为整数类型

private bool IsIntType(string dbType)

{

return dbType.ToLower() is "int" or "tinyint" or "bigint";

}

// 判断是否为字符串类型

private bool IsStringType(string dbType)

{

return dbType.ToLower() is "varchar" or "char" or "text" or "nvarchar" or "ntext";

}

// 获取C#类型(处理可空)

private string GetCSharpType(string dbType, string isNullable)

{

var baseType = dbType.ToLower() switch

{

"varchar" or "char" or "text" => "string",

"int" => "int",

"bigint" => "long",

"decimal" => "decimal",

"datetime" => "DateTime",

_ => "object"

};

// 可空类型(值类型+允许为NULL)

if (isNullable.Equals("YES", StringComparison.OrdinalIgnoreCase) && IsValueType(baseType))

{

return $"{baseType}?";

}

return baseType;

}

8.3 仓储类与服务类生成的封装

除实体类外,代码生成器还支持生成仓储类(数据访问层)和服务类(业务逻辑层),以仓储类为例:

复制代码

public string GenerateRepositoryCode(string entityName)

{

var codeBuilder = new StringBuilder();

codeBuilder.AppendLine("using SqlSugar;");

codeBuilder.AppendLine($"using {_namespace}.Entities;");

codeBuilder.AppendLine();

codeBuilder.AppendLine($"public class {entityName}Repository");

codeBuilder.AppendLine("{");

codeBuilder.AppendLine(" private readonly ISqlSugarClient _db;");

codeBuilder.AppendLine();

codeBuilder.AppendLine(" public {entityName}Repository(ISqlSugarClient db)");

codeBuilder.AppendLine(" {");

codeBuilder.AppendLine(" _db = db;");

codeBuilder.AppendLine(" }");

codeBuilder.AppendLine();

// 生成查询所有方法

codeBuilder.AppendLine(" /// <summary>");

codeBuilder.AppendLine(" /// 查询所有数据");

codeBuilder.AppendLine(" /// </summary>");

codeBuilder.AppendLine($" public List<{entityName}> GetAll()");

codeBuilder.AppendLine(" {");

codeBuilder.AppendLine($" return _db.Queryable<{entityName}>().ToList();");

codeBuilder.AppendLine(" }");

codeBuilder.AppendLine();

// 生成根据主键查询方法

codeBuilder.AppendLine(" /// <summary>");

codeBuilder.AppendLine(" /// 根据主键查询");

codeBuilder.AppendLine(" /// </summary>");

codeBuilder.AppendLine($" public {entityName} GetById(int id)");

codeBuilder.AppendLine(" {");

codeBuilder.AppendLine($" return _db.Queryable<{entityName}>().FindById(id);");

codeBuilder.AppendLine(" }");

codeBuilder.AppendLine();

// 生成新增、修改、删除方法(类似逻辑)

// ...

codeBuilder.AppendLine("}");

return codeBuilder.ToString();

}

九、总结:SQLSugar 封装的设计思想与优势

SQLSugar 的封装并非简单的 "API 包装",而是基于 "轻量高效、灵活扩展、易用性优先" 的设计思想,通过以下核心设计实现:

  1. 接口解耦:通过IDbAdapter、ICacheService等接口,实现多数据库、多缓存的灵活替换;
  1. 性能优化:反射缓存、表达式树缓存、连接池复用、批量 SQL,减少性能开销;
  1. 易用性封装:自动事务、代码生成、AOP 日志,降低开发者使用成本;
  1. 安全性保障:参数化 SQL 避免注入,事务确保数据一致性;
  1. 扩展性设计:AOP 拦截、自定义适配器,支持业务个性化扩展。

这些封装设计使 SQLSugar 在 "轻量级" 与 "功能全面" 之间取得平衡,既适合小型项目快速开发,也能支撑中大型项目的复杂需求,成为.NET 生态中 ORM 框架的优秀选择。

相关推荐
2301_803554528 小时前
mysql(自写)
数据库·mysql
叁沐9 小时前
MySQL 30 用动态的观点看加锁
mysql
柏油10 小时前
MySQL InnoDB 架构
数据库·后端·mysql
携欢11 小时前
PortSwigger靶场之Blind SQL injection with out-of-band data exfiltration
服务器·sql·microsoft
忧了个桑11 小时前
从Demo到生产:VIPER架构的生产级模块化方案
ios·架构
维基框架11 小时前
维基框架 (Wiki FW) v1.1.1 | 企业级微服务开发框架
java·架构
10km12 小时前
jsqlparser(六):TablesNamesFinder 深度解析与 SQL 格式化实现
java·数据库·sql·jsqlparser
JavaArchJourney12 小时前
MySQL 索引:原理篇
java·后端·mysql
Jasonakeke12 小时前
【重学 MySQL】九十三、MySQL的字符集的修改与底层原理详解
数据库·mysql·adb
小马哥编程14 小时前
【软考架构】SOA与微服务解疑
微服务·云原生·架构