1. 目标
DataTagTest 用于保存测试标签采集数据。采集数据按 CollectTime 做月分表,写入数据前由业务服务主动同步当前月份及未来月份的物理表,避免程序长期运行后因为未来月份表不存在导致插入失败。
当前设计不在程序启动时自动建表,建表动作由 DataTagTestService.AddAsync 在写入前触发。
2. 相关文件
| 文件 | 作用 |
|---|---|
NPP.IOT.Api.Contracts/Domain/DataCollection/DataTagTestEntity.cs |
定义采集数据实体、表名模板、分表规则和索引 |
NPP.IOT.Api/Core/Helper/DateShardingTableHelper.cs |
公共日期分表同步 Helper |
NPP.IOT.Api/Services/DataTagTest/DataTagTestService.cs |
写入采集数据前调用 Helper 同步物理分表 |
NPP.IOT.Host/Program.cs |
当前不再在启动阶段自动创建DataTagTest 分表 |
3. 实体设计
实体类:DataTagTestEntity
位置:NPP.IOT.Api.Contracts/Domain/DataCollection/DataTagTestEntity.cs
核心配置:
1 | [] |
说明:
Name = DbConsts.TableNamePrefix + "tag_test_{yyyyMM}"定义物理表名模板。DbConsts.TableNamePrefix当前是iot_。- 实际表名形如:
1 | iot_tag_test_202601 |
AsTable = "CollectTime=2026-1-1(1 month)"表示按CollectTime字段从2026-01-01开始,每 1 个月分一张表。CollectTime是分表字段,也是写入时决定落到哪张物理表的关键字段。
4. Helper 设计
公共 Helper:DateShardingTableHelper
位置:NPP.IOT.Api/Core/Helper/DateShardingTableHelper.cs
设计目的:
- 不把分表同步逻辑写死在
DataTagTestService中。 - 不再使用
DataTagTestTableSyncer这种只服务单个实体的同步器。 - 后续其他按日期分表的实体也可以复用同一个 Helper。
当前代码:
1 | using FreeSql; |
4.1 SyncFutureMonthlyTables 的含义
1 | DateShardingTableHelper.SyncFutureMonthlyTables<DataTagTestEntity>(_db, input.CollectTime, 1); |
含义是:
- 以
input.CollectTime所在月份为基准。 - 同步当前月。
- 再同步未来
futureMonthCount个月。
例如:
1 | baseTime = new DateTime(2028, 3, 15); |
会同步:
1 | iot_tag_test_202803 |
如果希望同步当前月 + 未来 3 个月,则调用:
1 | DateShardingTableHelper.SyncFutureMonthlyTables<DataTagTestEntity>(_db, input.CollectTime, 3); |
对应表:
1 | iot_tag_test_202803 |
4.2 SyncMonthlyTables 的执行流程
1 | DateShardingTableHelper.SyncMonthlyTables<DataTagTestEntity>(_db, startMonth, monthCount); |
执行步骤:
- 判断
monthCount <= 0时直接返回。 - 通过
typeof(TEntity)获取实体类型。 - 调用
db.CodeFirst.GetTableByEntity(entityType)获取 FreeSql 表元数据。 - 把传入时间规整到当月 1 号。
- 循环
monthCount次,每次增加 1 个月。 - 调用
table.AsTableImpl.GetTableNameByColumnValue(...)根据实体的AsTable配置计算真实物理表名。 - 调用
db.DbFirst.ExistsTable(tableName)判断表是否存在。 - 表不存在时调用
db.CodeFirst.SyncStructure(entityType, tableName)创建物理表。 - 最后通过
table.AsTableImpl.SetDefaultAllTables(...)设置当前实体默认参与查询的物理表范围。
5. Service 中如何调用生成表
服务类:DataTagTestService
位置:NPP.IOT.Api/Services/DataTagTest/DataTagTestService.cs
当前构造函数注入:
1 | private readonly IFreeSql _db; |
AddAsync 写入前同步分表:
1 | [] |
调用顺序:
1 | 接口收到新增请求 |
设计重点:
- 必须先同步表,再执行插入。
- 分表基准使用
input.CollectTime,不是服务器当前时间。 - 这样可以支持补录历史数据、写入未来采集时间等场景。
6. 程序启动时不自动建表
位置:NPP.IOT.Host/Program.cs
当前启动阶段配置:
1 | //配置FreeSql同步结构 |
说明:
- 启动时不调用
DateShardingTableHelper。 - 启动时不创建
DataTagTest分表。 - 表创建由
DataTagTestService.AddAsync在写入前按需触发。
这样可以避免程序启动时一次性创建大量未来表,也避免启动逻辑和某个业务实体强绑定。
7. 如何扩展到其他按日期分表实体
假设新增实体 TagDataEntity,只要实体上配置了 FreeSql 分表规则:
1 | [] |
在对应 Service 写入前调用:
1 | DateShardingTableHelper.SyncFutureMonthlyTables<TagDataEntity>(_db, input.CollectTime, 1); |
或同步固定月份范围:
1 | DateShardingTableHelper.SyncMonthlyTables<TagDataEntity>(_db, new DateTime(2028, 1, 1), 12); |
8. 注意事项
DateShardingTableHelper依赖实体上的 FreeSql[Table(..., AsTable = ...)]配置。SyncFutureMonthlyTables<TEntity>的futureMonthCount表示未来月份数量,不包含当前月。- 当前月会通过
futureMonthCount + 1一起同步。 SyncMonthlyTables<TEntity>会调用SetDefaultAllTables,影响后续当前实体默认查询的物理表范围。- 不建议在
Program.cs中为单个业务实体写自动建表逻辑。 - 插入前同步表可以保证长期运行时新月份表能被创建。
9. 当前 DataTagTest 的完整调用示例
1 | public async Task<long> AddAsync(DataTagTestAddInput input) |
如果 input.CollectTime = 2028-03-15,当前配置会确保以下表存在后再插入:
1 | iot_tag_test_202803 |
插入时 FreeSql 会根据 CollectTime 把数据写入:
1 | iot_tag_test_202803 |
#中台 #中台/数据库分表 #FreeSql #API接口 #仓储模式