RuoVea.ExSugar
10.0.0.8
dotnet add package RuoVea.ExSugar --version 10.0.0.8
NuGet\Install-Package RuoVea.ExSugar -Version 10.0.0.8
<PackageReference Include="RuoVea.ExSugar" Version="10.0.0.8" />
<PackageVersion Include="RuoVea.ExSugar" Version="10.0.0.8" />
<PackageReference Include="RuoVea.ExSugar" />
paket add RuoVea.ExSugar --version 10.0.0.8
#r "nuget: RuoVea.ExSugar, 10.0.0.8"
#:package RuoVea.ExSugar@10.0.0.8
#addin nuget:?package=RuoVea.ExSugar&version=10.0.0.8
#tool nuget:?package=RuoVea.ExSugar&version=10.0.0.8
🍬 RuoVea.ExSugar
SqlSugar ORM 一站式扩展库 — 集成 DI 注册、DbContext/Repository 模式、审计字段自动填充、全局查询过滤器、工作单元事务、差异日志记录、SM2 连接字符串加密,覆盖从 DataExecuting AOP 到分页查询的完整数据访问层抽象。
📖 目录
- 📋 概览
- 📦 安装
- ⚡ 30 秒快速开始
- 🧩 核心场景
- ⚙️ 配置选项详解
- 🏗️ 实体基类体系
- 📐 SQL 扩展方法
- 📄 分页查询
- 🧵 线程安全与生命周期
- ⚠️ 已知问题与注意事项
- 🗺️ 版本迁移指南
📋 概览
RuoVea.ExSugar 为 .NET 开发者提供 SqlSugar ORM 的开箱即用增强封装,通过 DI 容器统一管理数据库连接、审计字段、查询过滤器和事务边界。
┌─────────────────────────────────────────────────────────┐
│ RuoVea.ExSugar │
├─────────────────────────────────────────────────────────┤
│ DI 注册 实体基类 仓储模式 │
│ ├─ AddSqlSugarSetup ├─ EntityBase ├─ SugarRepository<T>
│ ├─ AddDbContextSetup ├─ EntityTenant └─ SimpleClient<T>
│ └─ AddInjectServiceSetup├─ AutoKeyBase │
│ └─ AutoKeyEntityBase │
│ │
│ 审计 AOP 全局过滤器 工作单元 │
│ ├─ DataExecuting ├─ 软删除过滤器 ├─ UnitOfWorkAttribute
│ ├─ 创建时间自动填充 ├─ 用户Id过滤器 └─ UnitOfWorkFilter
│ ├─ 修改时间自动填充 ├─ 租户Id过滤器 │
│ ├─ 雪花Id自动生成 └─ moreFilter │
│ └─ 租户Id自动设置 │
│ │
│ 差异日志 连接安全 扩展方法 │
│ ├─ OnDiffLogEvent ├─ SM2 加密解密 ├─ SqlSugarClient
│ ├─ UpdateWithDiffLog └─ IsEncrypt │ 扩展 (40+ 方法)
│ └─ InsertWithDiffLog └─ ISugarQueryable
│ 分页扩展 │
├─────────────────────────────────────────────────────────┤
│ 国际化: zh-CN │
└─────────────────────────────────────────────────────────┘
设计原则
| 原则 | 说明 |
|---|---|
| 约定优于配置 | AddSqlSugarSetup() 零参数启动,自动从 appsettings.json 读取 ConnectionConfigs 和 DataAuditing |
| AOP 透明拦截 | 审计字段在 DataExecuting 事件中自动填充,业务代码无需手动设置 CreateTime / ModifyTime |
| 过滤器可选 | 软删除、用户隔离、租户隔离均可通过 DbConnectionConfig 按连接独立开关 |
| 单例安全 | ISqlSugarClient 注册为 Singleton,SqlSugar 内部自行处理线程安全 |
📦 安装
.NET CLI
# .NET 8.0
dotnet add package RuoVea.ExSugar --version 8.0.0.33
# .NET 10.0
dotnet add package RuoVea.ExSugar --version 10.0.0.7
Package Manager
Install-Package RuoVea.ExSugar -Version 8.0.0.33
PackageReference
<PackageReference Include="RuoVea.ExSugar" Version="8.0.0.33" />
框架引用要求
RuoVea.ExSugar 需要 Microsoft.AspNetCore.App 框架引用(自动包含在 ASP.NET Core 项目中):
<FrameworkReference Include="Microsoft.AspNetCore.App" />
支持的 Target Framework
| TFM | 最低版本 | 关键依赖 |
|---|---|---|
net8.0 |
8.0.0.33 |
SqlSugarCore 5.1.*, Mapster 7.4.0, System.Linq.Dynamic.Core 1.7.2 |
net10.0 |
10.0.0.7 |
SqlSugarCore 5.1.*, Mapster 10.0.7, System.Linq.Dynamic.Core 1.7.2 |
传递依赖
| 包名 | 用途 |
|---|---|
SqlSugarCore (>= 5.1.*) |
ORM 核心 |
Mapster |
对象映射(DbContext 模式 / 分页 DTO 转换) |
System.Linq.Dynamic.Core |
动态 LINQ 表达式 |
System.Text.RegularExpressions |
正则表达式支持 |
Microsoft.Extensions.DependencyModel |
运行时程序集扫描 |
RuoVea.ExCache |
缓存抽象 |
RuoVea.ExDto |
DTO 基类(PageParam, ICurrentUser, ITenantEntity, IAuditableEntity, IDeletedEntity) |
RuoVea.ExIdGen |
雪花 Id 生成器 |
RuoVea.ExUtil |
通用工具(校验、字符串扩展) |
RuoVea.SM |
国密 SM2 加密(连接字符串解密) |
⚡ 30 秒快速开始
1. 配置 appsettings.json
{
"ConnectionConfigs": [
{
"ConfigId": "1300000000001",
"DbType": "MySql",
"ConnectionString": "Server=127.0.0.1;Database=MyDb;Uid=root;Pwd=123456;",
"IsAutoCloseConnection": true,
"EnableDiffLog": false,
"EnableUnderLine": true,
"IsEncrypt": false,
"IsDeleteFilter": true,
"IsUserIdFilter": false,
"IsTenantIdFilter": false,
"CommandTimeOut": 30
}
],
"DataAuditing": {
"CreateTime": "CreateTime",
"ModifyTime": "ModifyTime",
"Creator": "Creator",
"Modifier": "Modifier",
"TenantId": "TenantId",
"IsDelete": "IsDelete"
}
}
⚠️ 注意:
DataAuditing.TenantId的默认值是"CreateTime"(疑似 Bug),务必在配置文件中显式覆写为"TenantId"。
2. 注册服务
// Program.cs / Startup.cs
using RuoVea.ExSugar;
// <inheritdoc cref="SqlSugarSetup.AddSqlSugarSetup(IServiceCollection, bool, ICacheService, ServiceLifetime, Action{SqlSugarScopeProvider})"/>
// 方式一:从 appsettings.json 自动读取(最简)
builder.Services.AddSqlSugarSetup();
// 方式二:显式传入 IConfiguration
builder.Services.AddSqlSugarSetup(builder.Configuration);
// 方式三:代码构建配置
builder.Services.AddSqlSugarSetup(configs =>
{
configs.Add(new DbConnectionConfig
{
ConfigId = "1300000000001",
DbType = SqlSugar.DbType.MySql,
ConnectionString = "Server=127.0.0.1;Database=MyDb;Uid=root;Pwd=123456;",
EnableUnderLine = true
});
});
3. 使用 SugarRepository
// <inheritdoc cref="SugarRepository{T}"/>
public class UserService
{
private readonly SugarRepository<MyUser> _userRepo;
public UserService(SugarRepository<MyUser> userRepo)
{
_userRepo = userRepo;
}
public async Task<List<MyUser>> GetAllAsync()
{
return await _userRepo.GetListAsync(u => u.IsDelete == IsDelete.N, u => u.Id);
}
public async Task<MyUser?> GetByIdAsync(long id)
{
return await _userRepo.GetFirstAsync(u => u.Id == id, u => u.Id);
}
}
30 秒内你完成了: 配置文件编写 → DI 注册 → 使用泛型仓储查询数据。审计字段自动填充、软删除过滤、连接池管理全部由底层 AOP 接管。
🧩 核心场景
场景一:DbContext 模式
┌──────────────────┐
│ 自定义 DbContext │ 继承 abstract class DbContext
│ (e.g. MyDbCtx) │ 实现 IDbContext, IDisposable
└────────┬─────────┘
│ 注入
▼
┌──────────────────┐
│ services. │ AddDbContextSetup<MyDbCtx>(sp => new MyDbCtx(config))
│ AddDbContextSetup│
└──────────────────┘
// <inheritdoc cref="DbContext.DbContext(DbConnectionConfig, int, ICurrentUser, IRestFulLog, bool, bool, bool, Action{SqlSugarScopeProvider})"/>
/// <summary>
/// 自定义数据库上下文,继承抽象基类 DbContext
/// </summary>
public class MyDbContext : DbContext
{
public MyDbContext(DbConnectionConfig config,
ICurrentUser currentUser = null,
IRestFulLog restFulLog = null)
: base(config, commandTimeOut: 30,
currentUser: currentUser,
restFulLog: restFulLog,
userIdFilter: false,
tenantIdFilter: false,
deleteFilter: true)
{
}
// 使用 db 字段访问 SqlSugarClient
public List<MyUser> GetActiveUsers() =>
db.ToList<MyUser>(u => u.IsDelete == IsDelete.N);
}
// 注册
builder.Services.AddDbContextSetup(sp =>
{
var config = new DbConnectionConfig
{
ConfigId = "1300000000001",
DbType = SqlSugar.DbType.MySql,
ConnectionString = "Server=127.0.0.1;Database=MyDb;Uid=root;Pwd=123456;"
};
return new MyDbContext(config,
sp.GetService<ICurrentUser>(),
sp.GetService<IRestFulLog>());
});
⚠️ 注意:
DbContext构造函数内有两个重载 —— 一个接收DbConnectionConfig,另一个接收基类ConnectionConfig。推荐使用DbConnectionConfig重载以启用完整功能(差异日志、过滤器等)。第一个重载中存在 Bug:SugarUtil.SqlSugarClientUtil(db, ...)的db参数传入了未初始化的字段而非局部变量_db。
场景二:Repository 仓储模式
┌─────────────────────────┐
│ SugarRepository<T> │ 继承 SimpleClient<T>
│ ───────────────────── │ 自动排除 SystemTable 实体
│ ├─ GetPageResultAsync │
│ ├─ GetFirstAsync │
│ ├─ GetListAsync │
│ ├─ SumAsync │
│ ├─ Insert / InsertAsync │
│ └─ Update / UpdateAsync │
└─────────────────────────┘
// <inheritdoc cref="SugarRepository{T}.GetPageResultAsync"/>
// 分页查询
var (list, total, totalPage) = await _repo.GetPageResultAsync(
where: u => u.IsDelete == IsDelete.N,
pageNo: 1,
pageSize: 20,
order: u => u.CreateTime,
orderEnum: SordEnum.Desc
);
// <inheritdoc cref="SugarRepository{T}.GetFirstAsync"/>
// 获取最新一条
var latest = await _repo.GetFirstAsync(
where: u => u.Status == 1,
order: u => u.CreateTime,
orderEnum: SordEnum.Desc
);
// <inheritdoc cref="SugarRepository{T}.GetListAsync"/>
// 条件列表查询
var list = await _repo.GetListAsync(
where: u => u.Creator == userId,
order: u => u.Id
);
// <inheritdoc cref="SugarRepository{T}.SumAsync"/>
// 求和
var totalAmount = await _repo.SumAsync(
where: u => u.IsDelete == IsDelete.N,
expression: u => u.Amount
);
// <inheritdoc cref="SugarRepository{T}.Insert(T, bool, bool, object)"/>
// 插入(可选差异日志)
_repo.Insert(entity, ignoreNullValues: true, enableDiffLog: true);
// <inheritdoc cref="SugarRepository{T}.Update(T, bool, bool, object)"/>
// 更新(可选差异日志)
_repo.Update(entity, ignoreNullValues: true, enableDiffLog: true);
❗ 性能提示:
SugarRepository<T>构造函数中会检查SystemTableAttribute,如果实体标记了该特性则跳过特殊初始化。对于不需要租户/分库逻辑的简单实体,建议添加[SystemTable]特性以加速构造。
场景三:审计字段自动填充
┌──────────────────────────────────────────────────────────┐
│ DataExecuting AOP │
├──────────────────────────────────────────────────────────┤
│ InsertByObject: │
│ ├─ long Id == 0 → IdGenerator.Id (雪花Id) │
│ ├─ CreateTime == null → DateTime.Now │
│ ├─ IsDelete == null → IsDelete.N │
│ ├─ TenantId == null/0 → currentUser.TenantId │
│ └─ Creator == null/0 → currentUser.UserId │
│ │
│ UpdateByObject: │
│ ├─ ModifyTime == null → DateTime.Now │
│ └─ Modifier == null/0 → currentUser.UserId │
└──────────────────────────────────────────────────────────┘
// 实体定义
public class Order : EntityBase // 继承后自动获得审计能力
{
[SugarColumn(ColumnDescription = "订单编号")]
public string OrderNo { get; set; }
[SugarColumn(ColumnDescription = "订单金额")]
public decimal Amount { get; set; }
}
// 业务代码 —— 无需手动设置审计字段
var order = new Order
{
OrderNo = "ORD-20240623-001",
Amount = 1280.00m
// CreateTime / Creator / IsDelete / TenantId 由 AOP 自动填充
};
_sqlSugarClient.Insert(order); // AOP 自动注入审计值
order.Amount = 2560.00m;
_sqlSugarClient.Update(order); // ModifyTime / Modifier 自动更新
❗ 性能提示:
DataExecuting事件每次数据库操作都会触发。对于批量插入/更新的高性能场景,建议使用集合重载(Insert(List<T>)/Update(List<T>))以减少 AOP 回调次数,而非逐条调用。
场景四:全局查询过滤器
┌──────────────────────────────────────────┐
│ QueryFilter 自动注入 │
├──────────────────────────────────────────┤
│ IsDeleteFilter = true (默认) │
│ → WHERE IsDelete = N │
│ │
│ IsUserIdFilter = true (默认关闭) │
│ → WHERE Creator = currentUser.UserId │
│ │
│ IsTenantIdFilter = true (默认关闭) │
│ → WHERE TenantId = currentUser.TenantId│
│ │
│ ⚠️ 超级管理员 (IsSuperAdmin = true) 跳过 │
│ 所有过滤器 │
└──────────────────────────────────────────┘
// 配置每个连接的过滤器
var config = new DbConnectionConfig
{
ConfigId = "1300000000001",
DbType = SqlSugar.DbType.MySql,
ConnectionString = "...",
IsDeleteFilter = true, // 开启软删除过滤
IsUserIdFilter = false, // 关闭用户隔离
IsTenantIdFilter = true // 开启租户隔离
};
// 业务代码 —— 无感知过滤
var users = await _sqlSugarClient.ToListAsync<User>();
// 实际 SQL 自动追加: WHERE IsDelete = 0 AND TenantId = @tenantId
// 超级管理员查全量数据
if (currentUser.IsSuperAdmin)
{
// 所有过滤器被跳过,可查询所有 IsDelete=Y 的记录
}
⚠️ 超级管理员判断: 过滤器在
SugarUtil.ApplyQueryFilters中通过currentUser == null || currentUser.IsSuperAdmin判断是否跳过。确保ICurrentUser实现正确返回IsSuperAdmin,否则所有用户的过滤器都不会生效。
场景五:工作单元事务
┌──────────────────────────────────────────────┐
│ [UnitOfWork(IsolationLevel.ReadCommitted)] │
│ ┌─────────────────────────────────────────┐ │
│ │ Controller Action │ │
│ │ ├─ repo1.Insert(order) │ │
│ │ ├─ repo2.Update(inventory) │ │
│ │ └─ repo3.Insert(log) │ │
│ │ ↓ │ │
│ │ 无异常 → CommitTran() │ │
│ │ 有异常 → RollbackTran() → Dispose() │ │
│ └─────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
方式一:特性标注(推荐)
// <inheritdoc cref="UnitOfWorkAttribute"/>
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
private readonly SugarRepository<Order> _orderRepo;
private readonly SugarRepository<Inventory> _inventoryRepo;
public OrderController(
SugarRepository<Order> orderRepo,
SugarRepository<Inventory> inventoryRepo)
{
_orderRepo = orderRepo;
_inventoryRepo = inventoryRepo;
}
/// <summary>
/// 创建订单并扣减库存 —— 事务保证原子性
/// </summary>
[HttpPost]
[UnitOfWork(IsolationLevel.ReadCommitted)] // 标注即启用事务
public async Task<IActionResult> CreateOrder(OrderDto dto)
{
var order = new Order { /* ... */ };
await _orderRepo.InsertAsync(order);
var inventory = await _inventoryRepo.GetFirstAsync(
i => i.ProductId == dto.ProductId, i => i.Id);
inventory.Stock -= dto.Quantity;
await _inventoryRepo.UpdateAsync(inventory);
return Ok();
// 方法正常返回 → 自动 CommitTran
// 任意异常 → 自动 RollbackTran + Dispose
}
}
方式二:全局启用
// <inheritdoc cref="SqlSugarSetup.AddSqlSugarSetup(IServiceCollection, bool, ICacheService, ServiceLifetime, Action{SqlSugarScopeProvider})"/>
// isAllUnitOfWork = true 时,所有 Controller Action 自动包裹事务
builder.Services.AddSqlSugarSetup(isAllUnitOfWork: true);
⚠️ 注册顺序: 全局事务通过
services.AddMvcCore(options => options.Filters.Add(typeof(UnitOfWorkFilter)))注入,UnitOfWorkFilter的Order = 100。如果你的项目有自定义 Filter 且依赖事务执行顺序,请注意调序。
❗ 性能提示: 全局事务模式下,即使是
GET请求(只读操作)也会走事务流程(BeginTran→next()→CommitTran),增加不必要的数据库开销。建议仅在写操作 Controller 上使用[UnitOfWork]特性标注,而非全局启用。
场景六:差异日志记录
┌──────────────────────────────────────────┐
│ EnableDiffLog = true │
├──────────────────────────────────────────┤
│ Insert / Update / Delete 触发 │
│ ↓ │
│ OnDiffLogEvent 回调 │
│ ↓ │
│ IRestFulLog.DiffLog(dbType, diffData) │
│ ↓ │
│ 输出: BeforeData / AfterData / Sql │
│ / DiffType / Elapsed │
└──────────────────────────────────────────┘
// <inheritdoc cref="SqlSugarExtensions.UpdateWithDiffLog{T}(ISqlSugarClient, T, bool)"/>
// 更新并输出差异数据
_sqlSugarClient.UpdateWithDiffLog(entity);
// 输出:
// *****差异日志开始*****
// {
// "AfterData": [{"Columns": [{"ColumnName": "Amount", "Value": 2560}]}],
// "BeforeData": [{"Columns": [{"ColumnName": "Amount", "Value": 1280}]}],
// "BusinessData": null,
// "DiffType": "update",
// "Sql": "UPDATE `Order` SET `Amount`=@Amount WHERE `Id`=@Id",
// "Elapsed": 12
// }
// *****差异日志结束*****
// <inheritdoc cref="SqlSugarExtensions.InsertWithDiffLog{T}(ISqlSugarClient, T)"/>
_sqlSugarClient.InsertWithDiffLog(entity);
// <inheritdoc cref="SqlSugarExtensions.UpdateWithDiffLogAsync{T}(ISqlSugarClient, T, bool)"/>
await _sqlSugarClient.UpdateWithDiffLogAsync(entity);
自定义差异日志实现
// <inheritdoc cref="SqlSugarSetup.AddRestFulLogSetup{TFilterType}"/>
public class MyRestFulLog : RestFulLog
{
private readonly ILogger<MyRestFulLog> _logger;
public MyRestFulLog(ILogger<MyRestFulLog> logger) => _logger = logger;
public override void DiffLog(DbType dbType, DiffLogModel diffLogData)
{
// 写入结构化日志而非 Console
_logger.LogInformation("差异日志 {DiffType}: {Sql}",
diffLogData.DiffType,
UtilMethods.GetSqlString(dbType, diffLogData.Sql, diffLogData.Parameters));
}
public override void SugarExecutingLog(string sql, SugarParameter[] pars, string fullSql)
{
_logger.LogDebug("SQL 执行: {FullSql}", fullSql);
}
public override void SugarErrorLog(DbType dbType, string sql, SugarParameter[] pars, string message)
{
_logger.LogError("SQL 异常 [{DbType}]: {Message}\n{Sql}", dbType, message,
UtilMethods.GetSqlString(dbType, sql, pars));
}
}
// 注册自定义日志
builder.Services.AddRestFulLogSetup<MyRestFulLog>();
❗ 性能提示:
RestFulLog.DiffLogString中通过双层嵌套循环比较afterData和beforeData的每一列,时间复杂度为 O(n*m)。在列数较多的表(>50 列)上启用差异日志可能对性能产生可感知的影响。
场景七:连接字符串 SM2 加密
// <inheritdoc cref="DbConnectionConfig.IsEncrypt"/>
var config = new DbConnectionConfig
{
ConfigId = "1300000000001",
DbType = SqlSugar.DbType.MySql,
ConnectionString = "加密后的连接字符串(Base64)",
IsEncrypt = true, // 启用 SM2 解密
DbSecurity = "your-sm2-private-key" // SM2 私钥
};
// 内部自动调用:
// string dbString = SMEncryption.SM2Decrypt(item.ConnectionString, item.DbSecurity);
// connection.ConnectionString = dbString;
⚠️ 安全提示:
DbSecurity密钥切勿硬编码在代码中。建议通过环境变量、密钥管理服务(如 Azure Key Vault)或加密的配置文件注入。
⚙️ 配置选项详解
DbConnectionConfig
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
IsAutoCloseConnection |
bool |
true |
操作完成后自动关闭数据库连接 |
EnableDiffLog |
bool |
false |
是否启用差异日志(Insert/Update/Delete 前后对比) |
EnableUnderLine |
bool |
false |
是否启用驼峰转下划线(CreateTime → create_time) |
IsEncrypt |
bool |
false |
连接字符串是否经过 SM2 加密 |
DbSecurity |
string |
null |
SM2 解密私钥(IsEncrypt=true 时必填) |
IsDeleteFilter |
bool |
true |
是否启用软删除过滤(实体需实现 IDeletedEntity) |
IsUserIdFilter |
bool |
false |
是否启用用户 Id 过滤(实体需实现 ICreatorFilter 或继承 EntityBase) |
IsTenantIdFilter |
bool |
false |
是否启用租户 Id 过滤(实体需实现 ITenantIdFilter) |
CommandTimeOut |
int |
30 |
SQL 命令超时时间(秒) |
⚠️
CommandTimeOut单位是秒而非毫秒。默认 30 秒对于大多数 OLTP 操作足够,但对于复杂报表查询可能需要调大。
DataAuditing 审计字段映射
| 属性 | 默认值 | 说明 |
|---|---|---|
CreateTime |
"CreateTime" |
创建时间字段名(驼峰) |
ModifyTime |
"ModifyTime" |
修改时间字段名(驼峰) |
Creator |
"Creator" |
创建者字段名(驼峰) |
Modifier |
"Modifier" |
修改者字段名(驼峰) |
TenantId |
⚠️ "CreateTime" |
BUG: 默认值错误,应为 "TenantId" |
IsDelete |
"IsDelete" |
软删除标记字段名(驼峰) |
DbInitConfig
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
InitTable |
bool |
true |
是否自动初始化数据库表结构(CodeFirst) |
InitSeedData |
bool |
true |
是否自动初始化种子数据 |
SqlSugarConst 常量
| 常量 | 配置键 | 默认值 | 说明 |
|---|---|---|---|
MainConfigId |
SqlSugarConst:MainConfigId |
"1300000000001" |
默认主库标识 |
LogConfigId |
SqlSugarConst:LogConfigId |
"1300000000002" |
默认日志库标识 |
PrimaryKey |
SqlSugarConst:PrimaryKey |
"Id" |
默认主键名 |
DefaultTenantId |
SqlSugarConst:DefaultTenantId |
1300000000001 |
默认租户 Id |
🏗️ 实体基类体系
EntityBaseId AutoKeyBase
├─ Id : long (雪花Id) ├─ Id : int (自增)
└─ Check() └─ [SugarColumn(IsIdentity=true)]
↓ ↓
EntityBase : EntityBaseId AutoKeyEntityBase : AutoKeyBase
├─ CreateTime : DateTime? ├─ CreateTime : DateTime?
├─ ModifyTime : DateTime? ├─ ModifyTime : DateTime?
├─ Creator : long? ├─ Creator : long?
├─ Modifier : long? ├─ Modifier : long?
├─ IsDelete : IsDelete? ├─ IsDelete : IsDelete?
├─ IDeletedEntity ├─ IDeletedEntity
└─ IAuditableEntity └─ IAuditableEntity
↓
EntityTenant : EntityBase EntityTenantId : EntityBaseId
├─ TenantId : long? ├─ TenantId : long?
├─ CheckTenantId() ├─ CheckTenantId()
└─ ITenantEntity └─ ITenantEntity
| 基类 | 主键类型 | 审计字段 | 租户 | 软删除 | 适用场景 |
|---|---|---|---|---|---|
EntityBaseId |
long (雪花Id) |
-- | -- | -- | 纯主键实体 |
EntityBase |
long (雪花Id) |
✅ | -- | ✅ | 推荐 标准业务实体 |
EntityTenant |
long (雪花Id) |
✅ | ✅ | ✅ | 多租户业务实体 |
EntityTenantId |
long (雪花Id) |
-- | ✅ | -- | 仅需租户隔离的实体 |
AutoKeyBase |
int (自增) |
-- | -- | -- | 纯自增主键实体 |
AutoKeyEntityBase |
int (自增) |
✅ | -- | ✅ | 自增主键 + 审计的实体 |
⚠️ 已知问题:
EntityBaseData(数据权限实体基类,含CreateOrgId)和IOrgIdFilter接口已完全注释掉,如需数据权限过滤需自行实现。
实体接口
| 接口 | 成员 | 说明 |
|---|---|---|
IDeletedEntity |
IsDelete |
软删除标记 |
IAuditableEntity |
CreateTime, ModifyTime, Creator, Modifier |
审计字段 |
ITenantEntity |
TenantId |
租户隔离 |
ICreatorFilter |
Creator |
创建者过滤 |
ITenantIdFilter |
TenantId |
租户过滤 |
ICustormerEntityFilter |
AddEntityFilter() |
自定义过滤器扩展(返回 IEnumerable<TableFilterItem<object>>) |
📐 SQL 扩展方法
所有扩展方法均基于 SqlSugarClient(非 ISqlSugarClient),需在 using RuoVea.ExSugar; 后可用。
查询方法
| 方法 | 签名 | 说明 |
|---|---|---|
Count |
Count<T>(Expression<Func<T,bool>>) |
获取总数 |
CountAsync |
CountAsync<T>(Expression<Func<T,bool>>) |
异步总数 |
Any |
Any<T>(Expression<Func<T,bool>>) |
是否存在 |
AnyAsync |
AnyAsync<T>(Expression<Func<T,bool>>) |
异步是否存在 |
Single |
Single<T>(dynamic Id) |
主键查询 |
Single |
Single<T>(Expression<Func<T,bool>>) |
条件单条 |
SingleAsync |
SingleAsync<T>(Expression<Func<T,bool>>) |
异步条件单条 |
FirstOrDefault |
FirstOrDefault<T>(Expression<Func<T,bool>>) |
条件首条 |
FirstOrDefaultAsync |
FirstOrDefaultAsync<T>(Expression<Func<T,bool>>) |
异步条件首条 |
ToList |
ToList<T>() |
获取全部 |
ToList |
ToList<T>(Expression<Func<T,bool>>) |
条件列表 |
ToList |
ToList<T>(Expression, Expression, OrderByType) |
条件 + 排序列表 |
ToListAsync |
ToListAsync<T>() |
异步全部 |
ToListAsync |
ToListAsync<T>(Expression<Func<T,bool>>) |
异步条件列表 |
ToListAsync |
ToListAsync<T>(Expression, Expression, OrderByType) |
异步条件 + 排序 |
AsQueryable |
AsQueryable<T>() / AsQueryable<T>(predicate) |
返回 ISugarQueryable<T> |
AsEnumerable |
AsEnumerable<T>() / AsEnumerable<T>(predicate) |
返回 List<T> |
AsAsyncEnumerable |
AsAsyncEnumerable<T>() / AsAsyncEnumerable<T>(predicate) |
异步返回 List<T> |
IsExists |
IsExists<T>(Expression) |
表级存在判断 |
IsExistsAsync |
IsExistsAsync<T>(Expression) |
异步表级存在判断 |
IsTableExists |
IsTableExists<T>(string tableName) |
判断数据表是否存在 |
写入方法
| 方法 | 签名 | 说明 |
|---|---|---|
Insert |
Insert<T>(T entity, bool IgnoreColumns=true) |
插入单条 |
Insert |
Insert<T>(bool IgnoreColumns, params T[]) |
插入多条 |
Insert |
Insert<T>(IEnumerable<T>, bool IgnoreColumns) |
插入集合 |
InsertAsync |
InsertAsync<T>(T entity, bool IgnoreColumns) |
异步插入单条 |
InsertAsync |
InsertAsync<T>(bool IgnoreColumns, params T[]) |
异步插入多条 |
InsertAsync |
InsertAsync<T>(IEnumerable<T>, bool IgnoreColumns) |
异步插入集合 |
InsertReturnIdentity |
InsertReturnIdentity<T>(T, bool) |
插入并返回自增 Id (int) |
InsertReturnIdentityAsync |
InsertReturnIdentityAsync<T>(T, bool) |
异步插入并返回自增 Id (long) |
Update |
Update<T>(T entity, bool IgnoreColumns, string title) |
更新单条 |
Update |
Update<T>(bool IgnoreColumns, string title, params T[]) |
更新多条 |
Update |
Update<T>(IEnumerable<T>, bool IgnoreColumns, string title) |
更新集合 |
UpdateAsync |
UpdateAsync<T>(T entity, bool IgnoreColumns, string title) |
异步更新单条 |
UpdateAsync |
UpdateAsync<T>(bool IgnoreColumns, string title, params T[]) |
异步更新多条 |
UpdateAsync |
UpdateAsync<T>(IEnumerable<T>, bool IgnoreColumns, string title) |
异步更新集合 |
UpdateNoPrimaryKey |
UpdateNoPrimaryKey<T>(T, Expression columns, bool, string) |
无主键更新 |
UpdateNoPrimaryKeyAsync |
UpdateNoPrimaryKeyAsync<T>(T, Expression columns, bool, string) |
异步无主键更新 |
Delete |
Delete<T>(T entity, string title) |
删除实体 |
Delete |
Delete<T>(object key, string title) |
主键删除 |
Delete |
Delete<T>(params object[] keys) |
批量主键删除 |
Delete |
Delete<T>(Expression<Func<T,bool>>, string title) |
条件删除 |
DeleteAsync |
DeleteAsync<T>(T entity, string title) |
异步删除 |
DeleteAsync |
DeleteAsync<T>(object key, string title) |
异步主键删除 |
DeleteAsync |
DeleteAsync<T>(params object[] keys) |
异步批量删除 |
DeleteAsync |
DeleteAsync<T>(Expression<Func<T,bool>>, string title) |
异步条件删除 |
假删除(软删除)
// <inheritdoc cref="SqlSugarExtensions.FakeDelete{T}(ISqlSugarClient, T)"/>
// 实体必须继承 EntityBase
_sqlSugarClient.FakeDelete(entity);
// 效果: UPDATE xxx SET IsDelete='Y', ModifyTime=NOW(), Modifier=@userId WHERE Id=@id
// 或通过仓储调用
_repo.FakeDelete(entity);
// 异步版本
await _sqlSugarClient.FakeDeleteAsync(entity);
await _repo.FakeDeleteAsync(entity);
差异日志写入
| 方法 | 说明 |
|---|---|
UpdateWithDiffLog<T>(T, bool) |
更新并记录变更前后数据 |
UpdateWithDiffLogAsync<T>(T, bool) |
异步更新并记录差异 |
InsertWithDiffLog<T>(T) |
插入并记录差异 |
InsertWithDiffLogAsync<T>(T) |
异步插入并记录差异 |
排序构建器
// <inheritdoc cref="SqlSugarExtensions.OrderBuilder{T, Input}(ISugarQueryable{T}, Input, string, bool)"/>
var query = _sqlSugarClient.AsQueryable<Order>()
.OrderBuilder(pageParam, defualtSortField: "Id", descSort: true);
// 自动从 pageParam.Sidx / pageParam.Sord 读取排序规则
📄 分页查询
ToPageAsync(返回 PageResult)
// <inheritdoc cref="PagedExtensions.ToPageAsync{T}(ISugarQueryable{T}, int, int, bool)"/>
// 基础分页
var result = await _sqlSugarClient.AsQueryable<Order>()
.ToPageAsync(pageIndex: 1, pageSize: 20);
// result.Rows, result.TotalRows, result.PageIndex, result.PageSize
// <inheritdoc cref="PagedExtensions.ToPageAsync{T, Dto}(ISugarQueryable{T}, int, int)"/>
// 分页 + DTO 映射(自动 Mapster 转换)
var dtoResult = await _sqlSugarClient.AsQueryable<Order>()
.ToPageAsync<Order, OrderDto>(pageIndex: 1, pageSize: 20);
// <inheritdoc cref="PagedExtensions.ToPageAsync{T}(ISugarQueryable{T}, PageParam)"/>
// 从 PageParam 读取分页参数
var result = await _sqlSugarClient.AsQueryable<Order>()
.ToPageAsync(new PageParam { PageNo = 1, PageSize = 20 });
ToPagedList / ToPagedListAsync(返回 SqlSugarPagedList)
// <inheritdoc cref="SqlSugarPagedExtensions.ToPagedList{TEntity}(ISugarQueryable{TEntity}, int, int)"/>
var paged = _sqlSugarClient.AsQueryable<Order>().ToPagedList(1, 20);
// paged.Page, paged.PageSize, paged.Total, paged.TotalPages
// paged.Items, paged.HasPrevPage, paged.HasNextPage
// <inheritdoc cref="SqlSugarPagedExtensions.ToPagedListAsync{TEntity}(ISugarQueryable{TEntity}, int, int)"/>
var paged = await _sqlSugarClient.AsQueryable<Order>().ToPagedListAsync(1, 20);
SugarRepository 分页
// <inheritdoc cref="SugarRepository{T}.GetPageResultAsync"/>
var (list, total, totalPage) = await _repo.GetPageResultAsync(
where: u => u.IsDelete == IsDelete.N,
strWhere: "Amount > 100", // 可选:原生 SQL 条件
pageNo: 1,
pageSize: 20,
order: u => u.CreateTime,
orderEnum: SordEnum.Desc
);
🧵 线程安全与生命周期
| 组件 | 生命周期 | 线程安全 | 说明 |
|---|---|---|---|
ISqlSugarClient |
Singleton | ✅ 是 | SqlSugarCore 内部自行管理线程安全,无需担心并发 |
SugarRepository<T> |
Scoped (默认) | ✅ 是 | 默认跟随请求作用域,可通过 serviceLifetime 参数调整 |
DbContext |
Scoped (默认) | ⚠️ 非线程安全 | 内部持有 SqlSugarClient 实例引用,单例注册会导致并发问题 |
IRestFulLog |
Singleton | ⚠️ 视实现而定 | 默认 RestFulLog 使用 Console.Write,无共享状态 |
UnitOfWorkFilter |
Scoped | ✅ 是 | ASP.NET Core Filter 天然按请求隔离 |
IHttpContextAccessor |
Singleton | ✅ 是 | ASP.NET Core 内置线程安全 |
❗ 重要:
ISqlSugarClient以 Singleton 注册,与传统的 Scoped DbContext 模式不同。这是故意的设计选择 —— SqlSugar 的SqlSugarScope内部使用线程静态或 AsyncLocal 来隔离不同请求的数据库操作。不要将ISqlSugarClient注册改为 Scoped,这会导致SugarRepository<T>构造函数注入失败。
⚠️ 已知问题与注意事项
严重
| 问题 | 影响 | 解决方案 |
|---|---|---|
DataAuditing.TenantId 默认值错误 |
构造函数中 TenantId = "CreateTime" 而非 "TenantId",导致多租户场景下租户字段映射失效 |
务必在 appsettings.json 中显式覆写 "DataAuditing": { "TenantId": "TenantId" } |
EntityBaseData / IOrgIdFilter 已注释 |
数据权限(按部门过滤)功能不可用 | 需自行实现相关过滤逻辑 |
SqlSugarFilter 整体注释 |
机构范围过滤器(SetOrgEntityFilter)和自定义实体过滤器(SetCustomEntityFilter)不可用 |
如需使用,取消注释并适配当前版本 |
| DbContext DbConnectionConfig 构造函数 Bug | 第 64 行 SugarUtil.SqlSugarClientUtil(db, config, ...) 传入了未赋值的 db 字段而非局部变量 _db |
使用 ConnectionConfig 重载或自行修复 |
一般
| 问题 | 影响 | 解决方案 |
|---|---|---|
SetDbConfig 中 column.IsIgnore 仅处理 TenantId |
只根据 IsTenantIdFilter 控制 TenantId 字段的 IsIgnore,不对其他字段做动态忽略 |
其他字段的忽略逻辑自行通过 ConfigSugarColumn 特性控制 |
IgnoreColumns=true 参数实际含义 |
Insert/Update 方法的 IgnoreColumns=true 实际上是 ignoreAllNullColumns: true,即忽略值为 null 的列,而非忽略所有列 |
注意参数语义,确保传入非 null 的字段才会被写入 |
EnableUnderLine 依赖 UtilMethods.ToUnderLine |
驼峰转下划线依赖 SqlSugar 内置方法,行为跟随 SqlSugar 版本变化 | 升级 SqlSugar 时注意兼容性 |
性能建议
| 建议 | 说明 |
|---|---|
| 避免全局事务 | isAllUnitOfWork: true 会让所有请求(包括 GET)走事务,建议仅在写操作上使用 [UnitOfWork] 特性 |
| 批量操作使用集合重载 | Insert(IEnumerable<T>) / Update(IEnumerable<T>) 比逐个调用高效,减少 AOP 回调次数 |
| 按需启用差异日志 | EnableDiffLog = true 的 DiffLogString 有 O(n*m) 列比较开销,仅对审计敏感的表启用 |
非租户实体添加 [SystemTable] |
SugarRepository<T> 构造函数会对有 SystemTableAttribute 的实体跳过特殊初始化逻辑 |
🗺️ 版本迁移指南
从原生 SqlSugarCore 迁移
| 旧代码模式 | 迁移到 RuoVea.ExSugar |
|---|---|
new SqlSugarClient(connectionConfig) 手动管理 |
services.AddSqlSugarSetup() DI 注入 ISqlSugarClient |
手动设置 entity.CreateTime = DateTime.Now |
继承 EntityBase → AOP 自动填充 |
db.Queryable<T>().Where(u => u.IsDelete == N) 每次手写 |
配置 IsDeleteFilter = true → 全局自动过滤 |
db.Ado.BeginTran() 手动事务 |
[UnitOfWork] 特性或 UnitOfWorkFilter |
| 明文连接字符串 | IsEncrypt = true + SM2 加密 |
v8.0.x → v10.0.x
- API 无破坏性变化。v10.0.x 仅在
net10.0TFM 上编译,所有公开 API 签名保持一致。 - Mapster 版本从 7.4.0 升级到 10.0.7,如需自定义
TypeAdapterConfig,请参考 Mapster 10.x 迁移指南。 Microsoft.Extensions.DependencyModel从 8.0.* 升级到 10.0.*,行为无变化。
📄 License
MIT License © RuoVea
🔗 相关资源: NuGet Gallery · 问题反馈 · Gitee 仓库
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Mapster (>= 10.0.7)
- Microsoft.Extensions.DependencyModel (>= 10.0.9)
- RuoVea.ExCache (>= 10.0.0.3)
- RuoVea.ExDto (>= 10.0.0.4)
- RuoVea.ExIdGen (>= 10.0.0.3)
- RuoVea.ExUtil (>= 10.0.0.4)
- RuoVea.SM (>= 10.0.0.4)
- SqlSugarCore (>= 5.1.4.215)
- System.Linq.Dynamic.Core (>= 1.7.2)
NuGet packages (13)
Showing the top 5 NuGet packages that depend on RuoVea.ExSugar:
| Package | Downloads |
|---|---|
|
RuoVea.OmiApi.Config
系统配置管理 |
|
|
RuoVea.OmiApi.UserRoleMenu
用户角色菜单管理 |
|
|
RuoVea.OmiApi.SystemApp
系统应用管理 |
|
|
RuoVea.OmiApi.UserRole
用户角色管理 |
|
|
RuoVea.OmiApi.User
用户管理 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0.8 | 419 | 6/24/2026 |
| 10.0.0.7 | 495 | 5/28/2026 |
| 10.0.0.6 | 388 | 3/23/2026 |
| 10.0.0.5 | 303 | 1/27/2026 |
| 9.0.0.13 | 125 | 5/28/2026 |
| 9.0.0.11 | 1,132 | 1/27/2026 |
| 9.0.0.10 | 134 | 1/26/2026 |
| 8.0.0.34 | 808 | 6/24/2026 |
| 8.0.0.33 | 950 | 5/28/2026 |
| 8.0.0.32 | 918 | 3/23/2026 |
| 8.0.0.31 | 726 | 1/27/2026 |
| 7.0.0.33 | 585 | 5/28/2026 |
| 7.0.0.32 | 878 | 3/23/2026 |
| 7.0.0.31 | 961 | 1/27/2026 |
| 6.0.18.33 | 684 | 5/28/2026 |
| 6.0.18.32 | 1,081 | 3/23/2026 |
| 6.0.18.31 | 1,090 | 1/27/2026 |
| 5.0.1.20 | 99 | 5/28/2026 |
| 5.0.1.19 | 117 | 3/23/2026 |
| 5.0.1.18 | 127 | 1/27/2026 |