数据初始化与种子数据(Seed Data)
本页介绍 Ape‑Volo‑Admin 的数据库初始化与种子数据机制:启动时如何自动建库/建表、如何按顺序写入初始数据、如何初始化日志库与租户库,以及配置开关、幂等与常见问题。
总览
- 自动种子数据中间件:
DataSeederMiddleware(UseDataSeederMiddleware) - 具体初始化逻辑:
SeedService(主库/日志库/租户库) - 种子文件位置:
Ape.Volo.Api/wwwroot/resources/db/{TableName}.json - ORM:SqlSugar CodeFirst 建库建表,支持分表
SplitTableAttribute - 多租户:支持 ID 隔离与独立库(
TenantType.Id/TenantType.Db)
启用方式
在应用启动时调用中间件:
csharp
app.UseDataSeederMiddleware();触发条件(来自 SystemOptions / TenantOptions):
SystemOptions.IsInitTable = true时执行建库建表;SystemOptions.IsInitData = true时写入主库种子数据;SystemOptions.LogDataBase指定日志库连接名,用于日志库初始化;TenantOptions.Enabled = true且TenantOptions.Type == TenantType.Db时初始化每个租户的独立数据库与表。
建议:生产环境默认关闭
IsInitTable/IsInitData,在正式变更流程中使用迁移/脚本。
主库初始化(InitMasterDataAsync)
步骤:
- 打印环境信息与主库连接信息;
- 若非 Oracle,调用
DbMaintenance.CreateDatabase()建库; - 扫描实体并建表:
- 选择规则:带
[SugarTable]的实体;排除实现了ITenantEntity、带LogDataBaseAttribute、MultiDbTenantAttribute的实体; - 若
TenantOptions.Enabled && TenantOptions.Type == TenantType.Id,则额外包含实现了ITenantEntity的实体; - 对带
SplitTableAttribute的实体使用CodeFirst.SplitTables();否则CodeFirst.InitTables();
- 选择规则:带
- 当
IsInitData = true:按顺序导入种子数据(仅当对应表暂无数据时)。
已内置的种子顺序(节选):
- 用户
User、角色Role、菜单Menu、部门Department、岗位Job - 系统设置
Setting - 字典
Dict与字典详情DictDetail - 定时任务
QuartzNet - 邮箱账户
EmailAccount、邮件模板EmailMessageTemplate - 关系表:
UserRole、UserJob、RoleMenu、RoleApis - 接口
Apis - 租户
Tenant
幂等保证:每个类别插入前均执行 Queryable<T>().AnyAsync() 检查,无数据才导入。
种子文件:wwwroot/resources/db/{TableName}.json
json
// 以 sys_user 表为例(字段请与实体保持一致)
[
{
"Id": 1,
"UserName": "admin",
"NickName": "管理员",
"CreateTime": "2024-01-01 00:00:00"
}
]反序列化设置(要点):
- 日期格式:
yyyy-MM-dd HH:mm:ss - 忽略空值
- 自定义
CurrentDateTimeConverter
日志库初始化(InitLogData)
- 通过
SystemOptions.LogDataBase获取日志库连接;若未配置抛出异常:请在appsettings.json的DataConnection节点配置。 - 创建日志库(非 Oracle);
- 选择带
LogDataBaseAttribute的实体并建表; - 支持带日期占位符的表名:
{year}、{month}、{day},示例将{day}固定为01建立当月分表; - 分表实体同样使用
SplitTables()。
租户库初始化(InitTenantDataAsync)
- 仅当多租户启用且类型为独立库(
TenantType.Db)时执行; - 遍历
Tenant表中TenantType=Db的租户:- 构建/添加租户连接(SQLite 场景将相对路径拼接为
DataSource={ContentRoot}/path); - 创建库并为标注
MultiDbTenantAttribute的实体建表;
- 构建/添加租户连接(SQLite 场景将相对路径拼接为
- 每个租户库独立初始化,不导入主库的种子数据(除非你另行扩展)。
配置示例(建议放在 appsettings.Development.json)
json
{
"System": {
"IsInitTable": true,
"IsInitData": true,
"IsQuickDebug": true,
"LogDataBase": "LogDb"
},
"Tenant": {
"Enabled": false,
"Type": "Id" // 或 "Db"
}
}开发期可打开建库/数据开关;生产期请关闭并采用受控变更。
最佳实践与注意事项
- 安全开关:生产环境将
IsInitTable/IsInitData设为 false,避免二次初始化; - 备份优先:对已有库操作前先备份;
- 顺序依赖:种子顺序已考虑外键/依赖关系,扩展种子时保持一致(先主表、后关系表);
- 幂等与唯一索引:若手动导入过数据,确保唯一键与种子不冲突;
- 大表/分表:为日志等大表启用分表策略与归档;
- SQLite 路径:租户库的 SQLite 连接会拼接
ContentRootPath,注意读写权限; - 性能:大批量导入可调整批量提交、关闭多余日志(
RecordSqlEnabled=false)。
常见问题(FAQ)
- 为什么没有执行建库/导入数据?
- 检查
System.IsInitTable/IsInitData是否为 true; - 确认已在启动时调用
app.UseDataSeederMiddleware(); - 查看应用日志是否有异常(连接权限/路径等)。
- 检查
- 提示“未配置日志数据库”?
- 在
appsettings.json的DataConnection节点添加LogDataBase对应连接,并在System.LogDataBase指定连接名。
- 在
- Oracle 不支持建库?
- 是预期行为:请手动建库后再启动(代码内对 Oracle 抛出提示)。
- 多租户为 ID 隔离时是否会把
ITenantEntity表建在主库?- 是的,
TenantType.Id会将实现ITenantEntity的实体也建在主库。
- 是的,

