数据库操作
2023/5/23大约 5 分钟
数据库操作
本项目数据库采用 ORM Sqlsugar,数据仓储模式,对其简单的进行了封装操作。
数据仓储 (BaseRepository)
BaseRepository.cs 文件 - 继承 SimpleClient<T>,构造函数自动解析多租户数据库连接。
构造函数根据实体类型自动选择数据库连接,详见 SaaS 多租户文档。
查看 BaseRepository 完整代码
using Infrastructure;
using Infrastructure.Extensions;
using Mapster;
using SqlSugar;
using SqlSugar.IOC;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using ZR.Model;
namespace ZR.Repository
{
public class BaseRepository<T> : SimpleClient<T> where T : class, new()
{
public ITenant itenant = null;//多租户事务
public BaseRepository(ISqlSugarClient context = null) : base(context)
{
// 如果实现了公共数据库接口,则访问主库(ConfigId = "0")
if (App.IsTenantEnabled())
{
if (typeof(IMainDbEntity).IsAssignableFrom(typeof(T)))
{
Context = DbScoped.SugarScope.GetConnectionScope(App.MainDbConfigId); // 主库
}
else
{
var tenantId = App.GetCurrentTenantId();
Context = DbScoped.SugarScope.GetConnectionScope(tenantId);
}
}
else
{
//通过 [Tenant] 特性拿到 ConfigId,支持配置 key 引用
// [Tenant("MallDb")] → App.Configuration["MallDb"] ?? "MallDb" → "1"
// [Tenant("1")] → App.Configuration["1"] ?? "1" → "1"(向后兼容)
var tenantAttr = typeof(T).GetCustomAttribute<TenantAttribute>()?.configId;
if (tenantAttr != null)
{
var resolved = App.Configuration[tenantAttr.ParseToString()] ?? tenantAttr;
Context = DbScoped.SugarScope.GetConnectionScope(resolved);
}
else
{
Context = context ?? DbScoped.SugarScope.GetConnectionScope(App.MainDbConfigId);
}
}
//Context = DbScoped.SugarScope.GetConnectionScopeWithAttr<T>();
itenant = DbScoped.SugarScope;//设置租户接口
}
/// <summary>
/// 解析租户数据库连接:优先使用传入租户ID,其次使用当前请求租户;未启用多租户或租户无效时回退到当前Context。
/// </summary>
protected ISqlSugarClient ResolveTenantDb(string tenantId = null)
{
if (!App.IsTenantEnabled()) return Context;
var targetTenantId = string.IsNullOrWhiteSpace(tenantId)
? App.GetCurrentTenantId()
: tenantId.Trim();
if (string.IsNullOrWhiteSpace(targetTenantId)) return Context;
var configs = App.OptionsSetting?.DbConfigs;
var hasDbConfig = configs != null && configs.Exists(x =>
string.Equals(x.ConfigId, targetTenantId, StringComparison.OrdinalIgnoreCase));
if (!hasDbConfig) return Context;
return DbScoped.SugarScope.GetConnectionScope(targetTenantId);
}
/// <summary>
/// 解析主库连接(MainDb配置不存在时默认0)。
/// </summary>
protected ISqlSugarClient ResolveMainDb()
{
return DbScoped.SugarScope.GetConnectionScope(App.MainDbConfigId);
}
// ... 增删改查方法(与旧版基本一致,略)
}
// 分页查询扩展(新增 ToPageAsync 和 OrderByPropertyName 排序)
public static class QueryableExtension
{
public static PagedInfo<T> ToPage<T>(this ISugarQueryable<T> source, PagerInfo parm) { /* ... */ }
public static async Task<PagedInfo<T>> ToPageAsync<T>(this ISugarQueryable<T> source, PagerInfo parm) { /* ... */ }
public static PagedInfo<T2> ToPage<T, T2>(this ISugarQueryable<T> source, PagerInfo parm) { /* ... */ }
public static async Task<PagedInfo<T2>> ToPageAsync<T, T2>(this ISugarQueryable<T> source, PagerInfo parm) { /* ... */ }
}
}
使用事务
单库事务
/// <summary>
/// 修改数据权限信息
/// </summary>
public bool AuthDataScope(SysRoleDto sysRoleDto)
{
var result = UseTran(() =>
{
var oldMenus = SelectUserRoleMenus(sysRoleDto.RoleId);
var newMenus = sysRoleDto.MenuIds;
var arr_c = oldMenus.Intersect(newMenus).ToArray();
var delMenuIds = oldMenus.Where(c => !arr_c.Contains(c)).ToArray();
var addMenuIds = newMenus.Where(c => !arr_c.Contains(c)).ToArray();
RoleMenuService.DeleteRoleMenuByRoleIdMenuIds(sysRoleDto.RoleId, delMenuIds);
sysRoleDto.MenuIds = addMenuIds.ToList();
sysRoleDto.DelMenuIds = delMenuIds.ToList();
InsertRoleMenu(sysRoleDto);
});
if (!result.IsSuccess)
{
throw new Exception("提交数据异常," + result.ErrorMessage, result.ErrorException);
}
}
Action 级自动事务
项目还提供了 TransactionFilter ActionFilter,在 Action 执行前自动 BeginTranAsync(),成功提交,异常回滚,无需手动写 UseTran。
service 中使用 Ado
更多用法传送门
切库
- 方式 1、通过 Config 获取数据库,比较灵活
[SugarTable("sys_config", "配置表")]
[Tenant("0")] //对应ConfigId=0
public class SysConfig
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int ConfigId { get; set; }
public string ConfigName { get; set; }
}
var db1 = DbScoped.SugarScope.GetConnection(configId) //非线程安全
var db1 = DbScoped.SugarScope.GetConnectionScope(configId); //线程安全 (5.0.7.7)
- 方式 2、通过特性获取数据库,一个实体对一个库的时候非常方便
var db2=DbScoped.SugarScope.GetConnectionWithAttr<T>() //非线程安全
var db2=DbScoped.SugarScope.GetConnectionScopeWithAttr<T>() //线程安全(5.0.7.7)
更多使用传送门
- 方式 3
Repository 和Service 中切库
//使用配置为test的数据库
var db = itenant.GetConnectionScope("test");//线程安全
//var db = itenant.GetConnection("test");//非线程安全
var result = db.Ado.UseStoredProcedure().SqlQuery<SysUser>("proc_xxx");
var result2 = db.Queryable<SysUser>().ToList()
数据库初始化
appsettings.json 中配置:
| 配置项 | 说明 |
|---|---|
InitDb | true 时在开发环境下自动建表 + 导入种子数据 |
InitSeed | 建表后是否自动导入种子数据(data.xlsx) |
初始化流程
- 仅开发环境执行 — 生产环境跳过
- 交互式确认 — 非交互环境(CI/CD)直接执行;交互环境 30 秒超时等待回车确认
- CodeFirst 建表 —
InitDb()创建所有系统表(约 35 张),InitNewTb()补齐新增表 - 种子数据导入 —
InitSeedData()从wwwroot/data.xlsx自动导入初始数据 - 硬终止机制 — 初始化完成后不会自动把
InitDb改回false,下次启动仍会提示,需要开发者手动改回false。如果用户取消初始化,程序直接Environment.Exit(1)终止进程
数据库缓存
SqlSugar 的 DataInfoCacheService 支持两种缓存模式:
- 内存缓存 (
SqlSugarCache) — 默认模式 - Redis 缓存 (
SqlSugarRedisCache) — 需配置RedisServer.DbCache: true
分页查询中 RemoveDataCache() 在更新数据时自动清除相关缓存,MoreSettings.IsAutoRemoveDataCache = true 保证缓存一致性。
Aop 操作
文件 ZR.ServiceCore.SqlSugar.SqlsugarSetup
差异化日志(审计)

db.GetConnectionScope(configId).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
string name = App.UserName;
foreach (var item in editBeforeData)
{
var pars = db.Utilities.SerializeObject(item.Columns.ToDictionary(it => it.ColumnName, it => it.Value));
SqlDiffLog log = new()
{
BeforeData = pars,
BusinessData = data?.ToString(),
DiffType = diffType.ToString(),
Sql = sql,
TableName = item.TableName,
UserName = name,
AddTime = DateTime.Now,
ConfigId = configId
};
if (editAfterData != null)
{
var afterData = editAfterData?.First(x => x.TableName == item.TableName);
var afterPars = db.Utilities.SerializeObject(afterData?.Columns.ToDictionary(it => it.ColumnName, it => it.Value));
log.AfterData = afterPars;
}
db.GetConnectionScopeWithAttr<SqlDiffLog>()
.Insertable(log)
.ExecuteReturnSnowflakeId();
}
};
- 功能说明
用来记录增(没意义)、删、改变化之前的数据,更详细的介绍请看下方链接
如何使用
//删除
var response = _ArticleCategoryService.Delete([1], '文章删除');
//修改
Context.Updateable(sysUser).EnableDiffLogEvent("更新数据").ExecuteCommand();
//添加
Insertable(sysUser).EnableDiffLogEvent("添加").ExecuteReturnIdentity();
更多用于请参考 SQLsugar 文档
Sql 执行日志
OnLogExecuting 打印 SQL 日志(由 ShowDbLog 配置控制):
db.GetConnectionScope(configId).Aop.OnLogExecuting = (sql, pars) =>
{
if (!showDbLog) return;
var cmd = sql.TrimStart();
string log = $"【config={configId}】{UtilMethods.GetSqlString(config.DbType, sql, pars)}\n";
// ...
};
Sql 超时告警
OnLogExecuted 中监控 SQL 执行耗时,超过 sqlExecutionTime 秒时记录日志并通过企业微信告警。
数据库配置
appsettings.json 关键配置
{
"MainDb": "0", // 默认数据库 ConfigId
"InitDb": false, // 是否初始化数据库(仅开发环境生效)
"InitSeed": true, // 建表后是否自动导入种子数据
"ShowDbLog": false, // 是否打印 SQL 日志
"sqlExecutionTime": 5, // SQL 超时告警阈值(秒)
"dbConfigs": [
{
"Conn": "...",
"DbType": 1, // MySql=0, SqlServer=1, Oracle=3, PgSql=4
"ConfigId": "0", // 多租户唯一标识
"IsAutoCloseConnection": true
}
],
// 代码生成器独立连接
"CodeGenDbConfig": {
"Conn": "Data Source=...Initial Catalog={dbName};...", // {dbName} 为占位符
"DbType": 1,
"DbName": "ZrAdmin" // 默认库名
},
// Redis 数据库缓存
"RedisServer": {
"dbCache": false // true 时使用 Redis 缓存
}
}
DbType 枚举值
| 值 | 数据库类型 |
|---|---|
| 0 | MySql |
| 1 | SqlServer |
| 2 | Sqlite |
| 3 | Oracle |
| 4 | PostgreSQL |
