目录
[一、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 包装",而是基于 "轻量高效、灵活扩展、易用性优先" 的设计思想,通过以下核心设计实现:
- 接口解耦:通过IDbAdapter、ICacheService等接口,实现多数据库、多缓存的灵活替换;
- 性能优化:反射缓存、表达式树缓存、连接池复用、批量 SQL,减少性能开销;
- 易用性封装:自动事务、代码生成、AOP 日志,降低开发者使用成本;
- 安全性保障:参数化 SQL 避免注入,事务确保数据一致性;
- 扩展性设计:AOP 拦截、自定义适配器,支持业务个性化扩展。
这些封装设计使 SQLSugar 在 "轻量级" 与 "功能全面" 之间取得平衡,既适合小型项目快速开发,也能支撑中大型项目的复杂需求,成为.NET 生态中 ORM 框架的优秀选择。
