Skip to content

SqlSugar ORM

本页系统性介绍 Ape‑Volo‑Admin 中的 SqlSugar ORM 配置,帮助你快速理解多库、读写分离、实体映射、缓存、查询过滤器与 SQL 日志等能力如何落地。

你将获得

  • 如何注册 SqlSugar 并加载多数据源配置
  • CQRS 场景下的主从(读写分离)与从库权重
  • 实体映射规则(下划线命名、可空性、MySQL longtext)
  • 缓存策略与“写入即失效”
  • 全局查询过滤器:软删除/多租户/数据权限
  • AOP:写入前实体补全、SQL 日志记录、慢 SQL 告警与 MiniProfiler 集成

一、服务注册与配置来源

入口方法:AddSqlSugarService(this IServiceCollection services)

  • 读取配置:
    • SystemOptionsMasterDataBaseLogDataBaseUseRedisCacheIsCqrsIsQuickDebug
    • DataConnectionOptions.ConnectionItem:连接列表(ConnId、DbType、ConnectionString、Enabled、HitRate)
  • 校验:
    • 必须存在至少一个启用的连接;
    • 必须包含指定的主库 MasterDataBase 与日志库 LogDataBase
    • SQLite 连接会自动拼接为站点相对路径:DataSource=<ContentRootPath>/<file>
  • 构造 SqlSugarScope:为每个启用的连接创建 ConnectionConfig,并注册为单例 ISqlSugarClient

示例(在 Startup/Program 中注册):

csharp
services.AddSqlSugarService();

二、CQRS:读写分离(主从)

SystemOptions.IsCqrs == true

  • 会为“每个主库连接”自动收集同类型(DbType 相同)、不同 ConnId 的启用连接作为从库;
  • 若未找到从库,直接抛错提示需要至少一个从库;
  • 从库以 SlaveConnectionConfig 形式挂载到主库,支持 HitRate(访问权重)。

注意:

  • 日志库(LogDataBase)不会参与后续的 AOP 过滤与拦截(避免影响日志写入)。

三、实体映射与 Code-First 细节

ConfigureExternalServices 中:

  • 命名规则:将实体属性名按“驼峰转下划线”生成列名:UtilMethods.ToUnderLine
  • MySQL 兼容:若数据类型识别为 varchar(max),强制改为 longtext
  • 可空性:通过 NullabilityInfoContext 检测可写状态为可空时,设置列为 IsNullable = true
  • 其它:SqlServerCodeFirstNvarchar = true,使 SQL Server 默认使用 nvarchar

四、缓存策略

MoreSettings.IsAutoRemoveDataCache = true:写操作后自动清理相关数据缓存。

DataInfoCacheService 选择:

  • UseRedisCache = true 时使用 SqlSugarRedisCache(分布式 Redis 缓存);
  • 否则使用 SqlSugarDistributedCache(本地/分布式缓存抽象)。

五、全局查询过滤器

对“非日志库”的连接统一配置:

  1. 软删除过滤器
csharp
db.QueryFilter.AddTableFilter<ISoftDeletedEntity>(it => it.IsDeleted == false);

仅查询未删除的数据,要求实体实现 ISoftDeletedEntity

  1. 多租户过滤器(当前用户有租户时)
csharp
db.QueryFilter.AddTableFilter<ITenantEntity>(it => it.TenantId == App.HttpUser.TenantId);

要求实体实现 ITenantEntity

  1. 用户数据权限过滤器(放在最后)
csharp
// 从 IDataScopeService 取允许访问账号列表
// 按列表数量生成 CreateBy 的等值或 in 查询;为空则默认限制为当前账号
db.QueryFilter.AddTableFilter<ICreateByEntity>(...);

异常兜底:一旦数据权限计算异常,记录致命日志并回退为“仅当前账号”。

六、AOP:实体写入前补全(DataExecuting)

挂接位置:sugarScopeProvider.Aop.DataExecuting = DataExecuting;

逻辑要点:

  • 主键赋值:RootKey<long>Id=0 时自动分配雪花 Id;
  • 公共字段:
    • BaseEntity/BaseEntityNoDataScope:新增时补齐 CreateTime;更新时刷新 UpdateTime
    • 若存在登录用户:新增时补齐 CreateBy,更新时写入 UpdateBy
    • 若实体实现 ITenantEntity 且当前有租户,新增时回填 TenantId

这可以统一保证审计字段与多租户字段完整、可靠。

七、SQL 日志与性能分析

挂接位置:

  • 执行结束:Aop.OnLogExecuted = (sql, pars) => OnLogExecuted(...)(已启用)
  • 执行中:Aop.OnLogExecuting(示例代码中保留了注释,可按需开启)

行为说明(OnLogExecuted):

  • 开关:SerilogOptions.RecordSqlEnabled == true 时生效;
  • 输出:包含“用户、操作类型、数据库 ID、完整 SQL、耗时(ms)”;
  • 慢 SQL:当 ado.SqlExecutionTime.TotalMilliseconds > 10,以 Warning 输出并提示优化;
  • 集成:
    • Serilog:使用 LoggerPropertyConfiguration.Create.AddAopSqlProperty 写入结构化属性;
    • MiniProfiler:IsQuickDebug && MiniProfiler.Enabled 时展示 SQL 详情。

示例输出(简化):

执行DB--> 操作用户:[admin] 操作类型:[Insertable] 数据库ID:[master] INSERT ... VALUES ... [耗时]:12ms

建议:根据环境调整“慢 SQL 阈值”(默认 10ms 偏激进,生产可设为 50~200ms)。

八、为何排除日志库

allConnectionConfig.Where(x => x.ConfigId.ToString() != systemOptions.LogDataBase)

  • 日志库通常只承担写入,避免在其上套用过滤器与 AOP,减少干扰,提升日志写入稳定性。

九、常见配置对照

  • SystemOptions
    • MasterDataBase:主库标识;LogDataBase:日志库标识;
    • IsCqrs:开启主从;UseRedisCache:使用 Redis 缓存;IsQuickDebug:开发调试增强。
  • SerilogOptions
    • RecordSqlEnabled:是否记录 SQL 文本(配合 Serilog 输出开关 ToConsole/ToFile/ToDb 等)。
  • MiddlewareOptions
    • MiniProfiler.Enabled:是否启用 MiniProfiler。
  • DataConnectionOptions.ConnectionItem[*]
    • ConnIdDbTypeConnectionStringEnabledHitRate(从库权重)。

十、FAQ

  1. 为什么没有看到 SQL 日志?
  • 确认 SerilogOptions.RecordSqlEnabled = true
  • 检查 Serilog 的输出开关(ToConsole/ToFile/ToDb/ToElasticsearch)是否开启;
  • OnLogExecuted 已启用,若需“执行中”日志可开启 OnLogExecuting
  1. 软删除/多租户/数据权限没生效?
  • 实体需分别实现 ISoftDeletedEntityITenantEntityICreateByEntity
  • 当前登录上下文(App.HttpUser)是否存在、字段是否完整(Account/TenantId)。
  1. 读写分离报错或未生效?
  • IsCqrs=true 时必须配置至少一个从库(相同 DbType、不同 ConnId、Enabled=true);
  • 从库 HitRate 可调节访问分配比例。
  1. 审计字段没有自动写入?
  • 确认实体继承了 BaseEntity/BaseEntityNoDataScope 或包含对应字段;
  • 写入时机是 Aop.DataExecuting,请确认没有绕过 ORM 直接执行 SQL。
  1. 为什么日志库不加过滤器?
  • 避免对日志写入带来额外条件与性能影响;如确有需要,可在代码中单独处理日志库逻辑。

参考阅读:

版权所有 © 2021-2026 ApeVolo-Team