9. CodeFirst 建库建表与多库
启动时 AddSqlSugar() 会按 Database.json 的开关自动建库、建表、建视图、灌种子数据;实体上的 [SysTable]/[LogTable]/[Tenant] 决定它落在哪个库。
1. 全流程
services.AddSqlSugar() Admin.NET.Core/SqlSugar/SqlSugarSetup.cs
├─ AddYitIdHelper(snowIdOpt) 注册雪花 Id(StaticConfig.CustomSnowFlakeFunc)
├─ App.GetConfig<DbConnectionOptions>("DbConnection")
├─ 每个连接 SetDbConfig(config) 解密连接串、驼峰转下划线、SqlFunc 扩展、MoreSettings
├─ new SqlSugarScope(configs, db => { 每个库 SetDbAop + SetDbDiffLog })
├─ ITenant = sqlSugar 单例 ISqlSugarClient + Scoped SqlSugarRepository<>
├─ services.AddUnitOfWork<SqlSugarUnitOfWork>()
└─ 每个 config → InitDatabase(sqlSugar, config)
├─ EnableInitDb → dbProvider.DbMaintenance.CreateDatabase() (Oracle 跳过)
├─ EnableInitTable → InitTable(dbProvider, EnableIncreTable)
│ ├─ 先 DropView(防止视图挡住表结构同步)
│ ├─ 筛 App.EffectiveTypes 中贴 [SugarTable] 且未贴 [IgnoreTable] 的类
│ ├─ EnableIncreTable=true 时只保留贴 [IncreTable] 的类
│ ├─ 按 ConfigId 归属过滤(见第 3 节)
│ └─ 并行 dbProvider.InitTable(entityType)
├─ EnableInitView → InitView(dbProvider) (实现 ISqlSugarView 的类)
└─ EnableInitSeed → InitSeedData(dbProvider, EnableIncreSeed)
├─ 筛实现 ISqlSugarEntitySeedData<> 且未贴 [TenantSeed] 的类
├─ EnableIncreSeed=true 时只保留贴 [IncreSeed] 的类
├─ 按 [SeedData(order)] 升序
└─ 串行 dbProvider.InitTableSeedData(seedType) ★应用层种子必须最后执行2. 配置项逐项解释
// Admin.NET.Application/Configuration/Database.json
{
"DbConnection": {
"EnableConsoleSql": false, // 控制台彩色打印 SQL(调试神器,生产关掉)
"ConnectionConfigs": [
{
// "ConfigId": "1300000000001", // 默认主库标识,禁止修改(SqlSugarConst.MainConfigId)
"DbType": "Sqlite", // MySql/SqlServer/Sqlite/Oracle/PostgreSQL/Dm/Kdbndp/...
"ConnectionString": "DataSource=./Admin.NET.db",
"DbSettings": {
"EnableInitDb": false, // 建库(库不存在时自动创建;Oracle 不支持)
"EnableInitView": false, // 建视图(实现 ISqlSugarView 的类)
"EnableDiffLog": false, // 库表差异日志 → SysLogDiff(写入日志库)
"EnableUnderLine": false, // 驼峰属性名转下划线列名(CreateTime → create_time)
"EnableConnEncrypt": false // 连接串国密 SM2 加密(CryptogramHelper.SM2Decrypt)
},
"TableSettings": {
"EnableInitTable": false, // 全量建表/更新表结构
"EnableIncreTable": false // 只更新贴了 [IncreTable] 的表(生产推荐)
},
"SeedSettings": {
"EnableInitSeed": false, // 全量灌种子数据
"EnableIncreSeed": false // 只更新贴了 [IncreSeed] 的种子
}
}
]
}
}| 开关 | 开发环境 | 生产环境 |
|---|---|---|
EnableInitDb | 首次 true | false(库由运维建好) |
EnableInitTable | true | false |
EnableIncreTable | false | true + 新表贴 [IncreTable] |
EnableInitSeed | true | false |
EnableIncreSeed | false | true + 新种子贴 [IncreSeed] |
EnableConsoleSql | true | false |
EnableDiffLog | false | 按需(核心表) |
3. 库归属规则(最重要)
// SqlSugarSetup.InitTable 里的归属判定
if (config.ConfigId == SqlSugarConst.MainConfigId) // "1300000000001"
// 主库:贴 [SysTable] 的 + 既没贴 [LogTable] 也没贴 [Tenant] 的
entityTypes = entityTypes.Where(u =>
u.GetCustomAttributes<SysTableAttribute>().Any()
|| (!u.GetCustomAttributes<LogTableAttribute>().Any()
&& !u.GetCustomAttributes<TenantAttribute>().Any())).ToList();
else if (config.ConfigId == SqlSugarConst.LogConfigId) // "1300000000002"
entityTypes = entityTypes.Where(u => u.GetCustomAttributes<LogTableAttribute>().Any()).ToList();
else
// 业务分库:只取 [Tenant(configId)] 与本库 ConfigId 一致的
entityTypes = entityTypes.Where(u =>
u.GetCustomAttribute<TenantAttribute>()?.configId.ToString() == config.ConfigId.ToString()).ToList();实体未贴任何归属特性 ──────────────► 主库
实体贴 [SysTable] ─────────────────► 主库(强制,即使与其它特性共存也优先主库)
实体贴 [LogTable] ─────────────────► 日志库
实体贴 [Tenant("biz")] ────────────► ConfigId = "biz" 的业务库
同时贴 [SysTable] + [LogTable] ────► 主库(SysTable 分支优先匹配)仓储运行时选库顺序见 SqlSugarRepository<T> 构造函数:[Tenant] > [LogTable] > [SysTable] > 请求头 TenantId > Claims TenantId。
4. 多库配置示例
"ConnectionConfigs": [
{
"ConfigId": "1300000000001", // 主库
"DbType": "PostgreSQL",
"ConnectionString": "PORT=5432;DATABASE=adminnet;HOST=localhost;PASSWORD=xxx;USER ID=postgres",
"DbSettings": { "EnableInitDb": true, "EnableInitView": true, "EnableUnderLine": false },
"TableSettings": { "EnableInitTable": true, "EnableIncreTable": false },
"SeedSettings": { "EnableInitSeed": true, "EnableIncreSeed": false }
},
{
"ConfigId": "1300000000002", // 日志库(禁止修改此 Id)
"DbType": "PostgreSQL",
"ConnectionString": "PORT=5432;DATABASE=adminnet_log;HOST=localhost;PASSWORD=xxx;USER ID=postgres",
"DbSettings": { "EnableInitDb": true, "EnableDiffLog": true },
"TableSettings": { "EnableInitTable": true },
"SeedSettings": { "EnableInitSeed": false }
},
{
"ConfigId": "biz", // 业务分库(自定义 Id)
"DbType": "MySql",
"ConnectionString": "Server=localhost;Database=biz;Uid=root;Pwd=xxx;SslMode=None;",
"DbSettings": { "EnableInitDb": true },
"TableSettings": { "EnableInitTable": true },
"SeedSettings": { "EnableInitSeed": true }
}
]落到 biz 库的实体:
[SugarTable("biz_order", "业务订单表")]
[Tenant("biz")] // 明确指定业务库
public class BizOrder : EntityTenantBaseData { ... }5. 种子数据
// Admin.NET.Core/SeedData/SysPosSeedData.cs(真实代码)
[IgnoreUpdateSeed] // 只插入,不更新已有数据
public class SysPosSeedData : ISqlSugarEntitySeedData<SysPos>
{
public IEnumerable<SysPos> HasData()
{
var defaultTenantId = SqlSugarConst.DefaultTenantId;
return
[
new SysPos { Id = defaultTenantId, Name = "党委书记", Code = "dwsj", Type = "管理",
CreateTime = DateTime.Parse("2022-02-10 00:00:00"), Remark = "党委书记", TenantId = defaultTenantId },
new SysPos { Id = defaultTenantId + 10, Name = "局长", Code = "jz", Type = "管理",
CreateTime = DateTime.Parse("2022-02-10 00:00:00"), Remark = "局长", TenantId = defaultTenantId },
// ...
];
}
}种子相关特性:
| 特性 | 作用 |
|---|---|
[SeedData(100)] | 执行顺序(数字越大越晚执行) |
[IncreSeed] | 只在 EnableIncreSeed=true 时执行 |
[IgnoreUpdateSeed] | 只插入新记录,不更新已有记录 |
[IgnoreUpdateSeedColumn] | 更新时忽略该列(写在实体字段上) |
[TenantSeed] | 租户库专用种子(由 InitTenantData 处理,主流程跳过) |
种子目录:Admin.NET.Core/SeedData/(系统种子)、Admin.NET.Application/SeedData/(业务种子,最后执行,可覆盖系统种子)。
6. 视图
// Admin.NET.Application/Entity/TestViewSysUser.cs
[SugarTable(null, "用户表视图"), IgnoreTable] // ★ 必须加 [IgnoreTable]
public class TestViewSysUser : EntityBase, ISqlSugarView
{
[SugarColumn(ColumnDescription = "账号")]
public virtual string Account { get; set; }
[SugarColumn(ColumnDescription = "机构名称")]
public string? OrgName { get; set; }
public string GetQueryableSqlString(SqlSugarScopeProvider db)
{
return db.Queryable<SysUser>()
.LeftJoin<SysOrg>((u, a) => u.OrgId == a.Id)
.LeftJoin<SysPos>((u, a, b) => u.PosId == b.Id)
.Select((u, a, b) => new TestViewSysUser
{
Id = u.Id, Account = u.Account, RealName = u.RealName,
NickName = u.NickName, OrgName = a.Name, PosName = b.Name,
}).ToMappedSqlString();
}
}InitView 会先 DropView 再 CREATE VIEW,所以改视图 SQL 后只要开着 EnableInitView 重启即可。 InitTable 也会先删视图——因为视图依赖表,不删会导致表结构同步失败。
7. 读写分离
在连接配置里加 SlaveConnectionConfigs,按 HitRate 权重分摊读请求:
{
"ConfigId": "1300000000001",
"DbType": "Sqlite",
"ConnectionString": "DataSource=./Admin.NET.db",
"SlaveConnectionConfigs": [
{ "HitRate": 10, "ConnectionString": "DataSource=./Admin.NET1.db" },
{ "HitRate": 10, "ConnectionString": "DataSource=./Admin.NET2.db" }
]
}8. 动态加库(租户开通时建库)
// Admin.NET.Core/SqlSugar/SqlSugarSetup.cs
public static void InitTenantDatabase(DbConnectionConfig config)
{
SetDbConfig(config);
var iTenant = App.GetRequiredService<ISqlSugarClient>().AsTenant();
if (!iTenant.IsAnyConnection(config.ConfigId.ToString()))
iTenant.AddConnection(config); // 运行时追加连接
var db = iTenant.GetConnectionScope(config.ConfigId.ToString());
db.DbMaintenance.CreateDatabase();
// 建所有业务表:排除 [SysTable] [LogTable] [Tenant] [IgnoreTable]
foreach (var entityType in entityTypes)
{
var splitTable = entityType.GetCustomAttribute<SplitTableAttribute>();
if (splitTable == null) db.CodeFirst.InitTables(entityType);
else db.CodeFirst.SplitTables().InitTables(entityType);
}
InitTenantData(iTenant, config.ConfigId.ToLong(), config.ConfigId.ToLong());
}调用方:Admin.NET.Core/Service/Tenant/SysTenantService.cs:541。 InitTenantData 只处理贴了 [TenantSeed] 的种子,并自动把 TenantId/Pid/Id 改成当前租户的值。
9. 步骤清单:开发期接一个新库
Database.json新增ConnectionConfigs项,写ConfigId(主库/日志库 Id 禁止改)- 需要独立部署的实体贴
[LogTable]或[Tenant("configId")] - 打开新库的
EnableInitDb/EnableInitTable/EnableInitSeed - 写
SeedData/XxxSeedData.cs实现ISqlSugarEntitySeedData<Xxx>,用[SeedData(order)]排好顺序 - 启动,看控制台绿色输出「初始化表 xxx 耗时 xx ms」「初始化种子数据 xxx」
- 确认无误后把开关全部改回
false,保留EnableIncreTable/EnableIncreSeed - 后续新表/新种子贴
[IncreTable]/[IncreSeed]
⚠️ 注意事项 / 常见坑
- 生产环境务必关闭
EnableInitTable全量建表:它会逐表比对并ALTER,大表锁表风险高。改用EnableIncreTable+[IncreTable]。 - 实体忘了
[SugarTable]不会被建表(InitTable只筛贴了该特性的类)。 - 视图类忘了
[IgnoreTable]会被当成表创建,与视图名冲突报错。 [SysTable]优先级高于[LogTable]:两者同时贴会进主库。EnableUnderLine只在首次建表时生效,中途从false改true会生成新列而不是改名,等于数据丢失——建库前就定好。- 种子数据是串行执行的(注释明确写了"应用层种子必须最后执行,多线程顺序会乱"),不要在种子里做耗时操作。
- 种子里
Id建议显式赋值(如SqlSugarConst.DefaultTenantId + n),避免每次启动插入重复数据。 EnableConnEncrypt=true时连接串必须是CryptogramHelper.SM2Encrypt后的密文,明文会解密失败。- Sqlite 不支持
BulkCopy/部分DbMaintenance操作,生产建议 PostgreSQL / MySQL。 - 雪花 Id 的
WorkerId必须全局唯一(多实例部署时),否则主键冲突——见App.json的SnowId节点。
小结
建表 = 实体 + [SugarTable] + 开关;分库 = [SysTable]/[LogTable]/[Tenant];种子 = ISqlSugarEntitySeedData<T> + [SeedData(order)];视图 = ISqlSugarView + [IgnoreTable];租户库 = InitTenantDatabase + [TenantSeed]。开发期全开、生产期只留 EnableIncreTable/EnableIncreSeed。