# TORM **Repository Path**: ThingsGateway/TORM ## Basic Information - **Project Name**: TORM - **Description**: TORM 是一个为 .NET 平台设计的轻量级、高性能 ORM(对象关系映射)框架。它提供了简洁流畅的 API,支持多种数据库,并针对高性能场景进行了深度优化。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 11 - **Forks**: 4 - **Created**: 2026-04-05 - **Last Updated**: 2026-11-02 ## Categories & Tags **Categories**: database-dev **Tags**: None ## README 

TORM Logo

TORM - 高性能 .NET ORM 框架

[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![NuGet](https://img.shields.io/nuget/v/TORM.svg)](https://www.nuget.org/packages/TORM) [![.NET](https://img.shields.io/badge/.NET-8.0%20%7C%2010.0-blue.svg)](https://dotnet.microsoft.com/) **轻量级、高性能、功能丰富的对象关系映射框架** **TGateway** 生态系统中的 **ORM** 组件,专为高性能场景设计。
--- ## 中文 ### 📖 项目简介 TORM 是一个为 .NET 平台设计的轻量级、高性能数据访问组件。`TORM` 核心和四个关系型 provider 提供完整 ORM 能力;QuestDB、TDengine 与 Apache IoTDB 扩展包使用各数据库的原生协议,提供 schema、批量写入和查询能力,不伪装成关系型 ORM,也不暴露虚假的事务、Queryable 或 Upsert 语义。 ### ✨ 核心特性 #### 🚀 高性能设计 - **AOT**:支持AOT编译 - **表达式树解析**:高效的 LINQ 表达式到 SQL 转换 - **批量操作优化**:支持 BulkCopy 高性能批量插入/更新 #### 🗄️ 多数据库支持 | 数据库 | NuGet/项目 | 访问模型 | 当前能力 | | --- | --- | --- | --- | | MySQL / MariaDB | `TORM.MySql` | 关系型 ORM | CRUD、CodeFirst、事务、分表、BulkCopy、原子 Upsert | | SQL Server | `TORM.SqlServer` | 关系型 ORM | CRUD、CodeFirst、事务、分表、BulkCopy、原子 Upsert | | PostgreSQL | `TORM.PostgreSql` | 关系型 ORM | CRUD、CodeFirst、事务、分表、BulkCopy、原子 Upsert | | SQLite | `TORM.Sqlite` | 关系型 ORM | CRUD、CodeFirst、事务、分表和批量操作 | | QuestDB | `TORM.QuestDb` | 原生 REST SQL + CSV `/imp` | 独立 schema、表维护、TTL、临时 CSV 分批导入、拒绝行与提交状态、原生查询 | | TDengine | `TORM.TaosData` | 显式 Native / WebSocket | database、stable、subtable、KEEP、非 ADO row reader、Schemaless 写入结果 | | Apache IoTDB 2.0.8 | `TORM.IoTDB` | Native API Tree / Table | 双模型 schema、CodeFirst、Record/Tablet 分批写入、连接池和原生查询 | 关系型 ORM 与时序原生客户端是明确分层的两组能力。QuestDB、TDengine 和 IoTDB 不注册 `IDbProvider`,也不支持 `OrmClient.Queryable()`;需要完整 ORM 语义时应使用前四种关系型 provider。 #### 💡 丰富的功能 **基础 CRUD 操作** **高级功能** - 🔹 **Code First**:自动创建/更新数据库表结构 - 🔹 **分表分库**:支持时间分表、数量分表策略 - 🔹 **批量操作**:BulkCopy 高性能批量导入 - 🔹 **事务管理**:支持同步/异步事务 - 🔹 **AOP 拦截**:SQL 执行前后拦截,日志记录 - 🔹 **JSON 支持**:原生 JSON 类型支持 - 🔹 **全局分表语义**:实体更新/删除按分表列路由,Distinct、Count、排序和分页在跨表结果上统一执行 - 🔹 **一致写入列策略**:Insertable、Savable 和各关系型 BulkCopy 统一处理 identity 与 `IsOnlyIgnoreInsert` - 🔹 **安全 SQL 方言**:标识符转义、LIKE 字面量转义和比较方式由 provider 方言配置,不在表达式访问器中硬编码数据库类型 - 🔹 **并发保存**:数据库原生 Upsert 保持原子语义,fallback 的存在性查询和写入复用同一连接与事务 ### ⏱️ 时序数据库原生客户端 #### QuestDB `TORM.QuestDb` 保留 QuestDB REST `/exec` 查询和 `/imp` 导入协议。每个导入批次使用一个临时 CSV 文件并在成功、拒绝、取消或异常后删除;只有服务端明确接受后才返回 `Committed`,部分拒绝和全部拒绝都会抛出包含数量与原始响应的异常。 ```csharp using QuestDbDriver; using TORM.QuestDb; using var client = new QuestDbClient(new ConnectionStringBuilder("host=127.0.0.1;port=9000")); var tables = new QuestDbTableClient(client); await tables.EnsureTableAsync(); var writer = new QuestDbCsvBulkWriter(client) { BatchSize = 1000 }; var result = await writer.WriteAsync(rows); if (result.CommitState != QuestDbCommitState.Committed) throw new InvalidOperationException("QuestDB batch was not committed."); ``` #### Apache IoTDB 连接串必须显式指定 `Model=tree` 或 `Model=table`。Tree 模型需要 `RootPath`,Table 模型需要 `Database`;同一个客户端不会在两种模型之间自动探测或失败回退。 ```csharp using TORM.IoTDB; var options = IoTdbConnectionOptions.Parse( "DataSource=127.0.0.1;Port=6667;Username=root;Password=root;" + "Model=tree;RootPath=root.gateway;PoolSize=2;FetchSize=128;ZoneId=UTC"); await using var client = new IoTdbClient(options); await client.TreeCodeFirst.InitDeviceSchemaAsync("device_1001"); var writer = client.CreateTreeWriter("device_1001"); writer.BatchSize = 500; await writer.InsertAsync(points); ``` Table 模型使用 `IoTdbTableSchema`、`TableCodeFirst` 和 `CreateTableWriter()`;Tree 模型使用 device path、measurement、`TreeCodeFirst` 和 `CreateTreeWriter()`。两种模型的查询都返回 `IoTdbQueryResult`,时间以 UTC 毫秒进入原生协议。 #### TDengine TDengine 连接必须显式选择 `Protocol=Native` 或 `Protocol=WebSocket`。当前客户端直接管理 transport 和结果生命周期,不再提供 `DbConnection`、`DbCommand`、`DbDataReader` 或 `TaosDataOrmClient` 兼容外壳。 ```csharp using TORM.TaosData; var options = TdengineConnectionOptions.Parse( "Host=127.0.0.1;Port=6041;Username=root;Password=taosdata;Protocol=WebSocket"); using var client = new TdengineClient(options); if (!client.CheckHealth()) throw new InvalidOperationException("TDengine is unavailable."); var schema = new TdengineSchemaClient(client); schema.EnsureDatabase("telemetry"); using var reader = client.Query("SELECT server_version() AS version"); ``` Native/WebSocket 的真实异步和取消能力由 `client.Capabilities` 明确报告;客户端不会用 `Task.Run` 伪造异步,也不会在协议失败时自动切换 transport。 ### 📊 性能测试 TORM 与国内主流 ORM 框架 的Sqlite性能对比测试结果: ``` BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8037/25H2/2025Update/HudsonValley2) Intel Core Ultra 9 285H 2.90GHz, 1 CPU, 16 logical and 16 physical cores .NET SDK 10.0.103 [Host] : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 .NET 10.0 : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v3 Job=.NET 10.0 Runtime=.NET 10.0 ``` | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | |--------------------------- |-----------------:|----------------:|----------------:|-----------------:|------:|--------:|------------:|-----------:|----------:|-------------:|------------:| | TORM_BulkInsert_10000 | 29,483.870 μs | 564.6185 μs | 554.5309 μs | 29,662.345 μs | 1.00 | 0.03 | 1062.5000 | - | - | 13392.2 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_BulkInsert_10000 | 54,989.997 μs | 1,048.1157 μs | 1,076.3376 μs | 55,272.167 μs | 1.00 | 0.03 | 2833.3333 | 1000.0000 | 166.6667 | 34263.76 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_BulkInsert_10000 | 537,387.137 μs | 14,137.7720 μs | 41,240.5475 μs | 553,427.500 μs | 1.01 | 0.12 | 5000.0000 | 1000.0000 | - | 64690.97 KB | 1.00 | | | | | | | | | | | | | | | TORM_BulkInsert_50000 | 156,769.159 μs | 1,091.2339 μs | 967.3505 μs | 156,778.888 μs | 1.00 | 0.01 | 5250.0000 | - | - | 66934.26 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_BulkInsert_50000 | 268,856.227 μs | 3,064.4750 μs | 2,866.5118 μs | 269,124.600 μs | 1.00 | 0.01 | 14000.0000 | 4000.0000 | 1000.0000 | 168710.47 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_BulkInsert_50000 | 2,791,199.208 μs | 55,809.9648 μs | 97,746.6592 μs | 2,802,945.200 μs | 1.00 | 0.05 | 26000.0000 | 1000.0000 | - | 323409.88 KB | 1.00 | | | | | | | | | | | | | | | TORM_BulkUpdate_50000 | 99,459.911 μs | 967.6688 μs | 905.1580 μs | 99,733.850 μs | 1.00 | 0.01 | 5333.3333 | - | - | 66933.79 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_BulkUpdate_50000 | 182,485.632 μs | 2,967.7149 μs | 2,630.8020 μs | 182,848.775 μs | 1.00 | 0.02 | 14500.0000 | 4000.0000 | 1500.0000 | 168710.39 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_BulkUpdate_50000 | 2,588,248.118 μs | 51,038.1952 μs | 52,412.4689 μs | 2,598,517.600 μs | 1.00 | 0.03 | 42000.0000 | 1000.0000 | - | 521759.37 KB | 1.00 | | | | | | | | | | | | | | | TORM_DeleteBatch_10000 | 7.996 μs | 0.0541 μs | 0.0480 μs | 8.006 μs | 1.00 | 0.01 | 0.1831 | - | - | 2.4 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_DeleteBatch_10000 | 69.195 μs | 1.0972 μs | 1.0263 μs | 69.525 μs | 1.00 | 0.02 | 0.9155 | - | - | 11.49 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_DeleteBatch_10000 | 65.489 μs | 0.5363 μs | 0.4754 μs | 65.528 μs | 1.00 | 0.01 | 0.5493 | - | - | 7.01 KB | 1.00 | | | | | | | | | | | | | | | TORM_GetList | 9.035 μs | 0.0527 μs | 0.0493 μs | 9.045 μs | 1.00 | 0.01 | 0.5035 | - | - | 6.2 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_GetList | 90.299 μs | 1.7914 μs | 3.6995 μs | 90.835 μs | 1.00 | 0.07 | 2.0752 | 0.9766 | - | 25.61 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_GetList | 66.647 μs | 1.3142 μs | 2.2316 μs | 67.329 μs | 1.00 | 0.05 | 0.8545 | 0.7935 | - | 10.76 KB | 1.00 | | | | | | | | | | | | | | | TORM_InsertBatch_10000 | 29,821.968 μs | 117.4661 μs | 104.1306 μs | 29,839.409 μs | 1.00 | 0.00 | 1062.5000 | - | - | 13391.97 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_InsertBatch_10000 | 119,720.489 μs | 1,983.6632 μs | 1,758.4657 μs | 119,777.158 μs | 1.00 | 0.02 | 6500.0000 | 3166.6667 | 1000.0000 | 72158.74 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_InsertBatch_10000 | 562,874.180 μs | 3,067.5205 μs | 2,869.3606 μs | 562,098.900 μs | 1.00 | 0.01 | 5000.0000 | 1000.0000 | - | 64691.03 KB | 1.00 | | | | | | | | | | | | | | | TORM_InsertBatch_50000 | 156,858.300 μs | 1,981.4147 μs | 1,756.4725 μs | 156,255.650 μs | 1.00 | 0.02 | 5000.0000 | - | - | 66934.18 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_InsertBatch_50000 | 587,029.233 μs | 8,298.3128 μs | 6,478.7768 μs | 587,832.350 μs | 1.00 | 0.02 | 28000.0000 | 13000.0000 | 2000.0000 | 343406.52 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_InsertBatch_50000 | 2,743,381.907 μs | 22,418.0622 μs | 19,873.0282 μs | 2,748,549.050 μs | 1.00 | 0.01 | 26000.0000 | 1000.0000 | - | 323409.86 KB | 1.00 | | | | | | | | | | | | | | | TORM_SaveBatchUpsert_1000 | 2,488.677 μs | 13.5737 μs | 11.3346 μs | 2,487.567 μs | 1.00 | 0.01 | 109.3750 | - | - | 1361.35 KB | 1.00 | | | | | | | | | | | | | | | TORM_SaveBatch_1000 | 3,214.556 μs | 52.0409 μs | 48.6791 μs | 3,198.125 μs | 1.00 | 0.02 | 156.2500 | 15.6250 | - | 2098.54 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_SaveBatch_1000 | 116,875.679 μs | 2,299.9455 μs | 2,361.8747 μs | 116,837.275 μs | 1.00 | 0.03 | 7500.0000 | 1000.0000 | - | 96188.28 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_SaveBatch_1000 | 6,921.216 μs | 136.1796 μs | 127.3824 μs | 6,879.202 μs | 1.00 | 0.03 | 234.3750 | 117.1875 | 70.3125 | 2566.44 KB | 1.00 | | | | | | | | | | | | | | | TORM_SaveBulk_50000 | 130,866.535 μs | 2,607.0517 μs | 7,003.6756 μs | 132,598.750 μs | 1.00 | 0.08 | 5500.0000 | - | - | 67716.66 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_SaveBulk_50000 | 6,335,388.058 μs | 124,308.3524 μs | 170,154.6902 μs | 6,390,547.400 μs | 1.00 | 0.04 | 712000.0000 | 60000.0000 | 9000.0000 | 8634677.2 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_SaveBulk_50000 | 199,689.818 μs | 3,965.4261 μs | 11,184.5554 μs | 202,523.525 μs | 1.00 | 0.08 | 8500.0000 | 2000.0000 | 500.0000 | 129874.98 KB | 1.00 | | | | | | | | | | | | | | | TORM_UpdateBatch_1000 | 2,059.663 μs | 13.8345 μs | 12.9408 μs | 2,055.851 μs | 1.00 | 0.01 | 109.3750 | - | - | 1344.37 KB | 1.00 | | | | | | | | | | | | | | | SqlSugar_UpdateBatch_1000 | 53,523.906 μs | 1,172.7374 μs | 3,364.8036 μs | 54,097.327 μs | 1.01 | 0.11 | 909.0909 | 727.2727 | 181.8182 | 11012.93 KB | 1.00 | | | | | | | | | | | | | | | FreeSql_UpdateBatch_1000 | 53,324.506 μs | 650.2690 μs | 608.2620 μs | 53,067.340 μs | 1.00 | 0.02 | 800.0000 | 200.0000 | - | 10461.04 KB | 1.00 | 可以看出,TORM无论是速度还是内存占用都表现优异,得益于TORM是一个没有任何历史包袱的项目,其高性能设计和批量操作优化,在大数据量场景下表现尤为突出。 > 注:实际性能因数据库类型、数据量、硬件配置等因素会有所不同。建议在实际项目环境中进行测试。 ## 项目结构 ```text TORM/ |-- analyzer/TORM.Generators/ 源生成器 |-- src/TORM/ ORM 核心 |-- src/TORM.Sqlite/ SQLite 驱动 |-- src/TORM.MySql/ MySQL/MariaDB 驱动 |-- src/TORM.SqlServer/ SQL Server 驱动 |-- src/TORM.PostgreSql/ PostgreSQL 驱动 |-- src/TORM.QuestDb/ QuestDB REST SQL、schema 与 CSV writer |-- src/TORM.IoTDB/ Apache IoTDB Native API Tree/Table 双模型扩展 |-- src/TORM.TaosData/ TDengine Native/WebSocket 原生客户端 |-- samples/TORM.AotDemo/ AOT 示例 |-- benchmark/TORM.Benchmark/ BenchmarkDotNet 基准项目 `-- test/TORM.Test/ 自动化测试 ``` ## 构建与测试 ```bash dotnet restore TORM.slnx dotnet build TORM.slnx -c Release dotnet test test/TORM.Test/TORM.Test.csproj -c Release ``` 真实数据库集成测试使用 SQLite 加六个 Docker 服务:MySQL、PostgreSQL、SQL Server、QuestDB、TDengine 和 Apache IoTDB 2.0.8。固定镜像、端口、账号和连接串保存在 `test/TORM.Test/docker-compose.yml` 与 `test/TORM.Test/docker-test.env`。 ```powershell docker compose -f test/TORM.Test/docker-compose.yml up -d docker compose -f test/TORM.Test/docker-compose.yml ps dotnet test test/TORM.Test/TORM.Test.csproj --configuration Release ``` 完整连接变量和 Windows Docker Desktop 注意事项见 [数据库集成测试说明](test/TORM.Test/README.docker-tests.md),当前生产变更与测试名称的逐项映射见 [测试覆盖矩阵](test/TORM.Test/TEST_COVERAGE_MATRIX.md)。 2026-08-30 的完整 Release 实测结果为 `3808/3808` 通过、`0` 失败、`0` 跳过;测试覆盖四种关系型 provider 的真实 CRUD/事务/分表/索引/BulkCopy,以及 QuestDB、TDengine、IoTDB 的真实原生协议闭环。 运行基准测试: ```bash dotnet run -c Release --project benchmark/TORM.Benchmark/TORM.Benchmark.csproj ``` #### 基础配置 ```csharp using TORM; // 创建 ORM 客户端 using var ormClient = new OrmClient(new OrmConnectionConfig { DatabaseType = OrmDbType.MySql, ConnectionString = "Server=localhost;Database=test;Uid=root;Pwd=password;" }); ``` #### 定义实体 ```csharp [OrmTable(TableName = "users")] public class User { [OrmColumn(IsPrimaryKey = true, IsIdentity = true)] public long Id { get; set; } [OrmColumn(ColumnName = "user_name", Length = 50)] public string Name { get; set; } public int Age { get; set; } public DateTime CreateTime { get; set; } } ``` #### Code First 自动建表 ```csharp // 自动创建表结构 await ormClient.CodeFirst.InitTableAsync(); ``` ### 📚 功能详解 #### 查询操作 ```csharp // 基础查询 var list = await ormClient.Queryable().ToListAsync(); // 条件查询 var adults = await ormClient.Queryable() .Where(u => u.Age >= 18) .ToListAsync(); // 分页查询(返回 PagedList,包含总数) var page = await ormClient.Queryable() .Where(u => u.IsActive) .OrderByDescending(u => u.CreateTime) .ToPageListAsync(pageNumber: 1, pageSize: 20); // 聚合查询 var count = await ormClient.Queryable().CountAsync(); var exists = await ormClient.Queryable().AnyAsync(); // 投影查询 var names = await ormClient.Queryable() .Select(u => new { u.Id, u.Name }) .ToListAsync(); // 流式查询(大数据量场景) await foreach (var user in ormClient.Queryable().ToAsyncEnumerable()) { // 逐行处理 } ``` #### 批量操作 ```csharp // 批量插入(高性能) await ormClient.InsertableRange(users).ExecuteAsync(); // BulkCopy 批量导入(最高性能) await ormClient.BulkCopy().BulkInsertAsync(users); // 批量更新 await ormClient.BulkCopy().BulkUpdateAsync(users); // 批量保存(自动判断插入/更新) await ormClient.BulkCopy().BulkMergeAsync(users); ``` #### 分表操作 **定义分表实体:** ```csharp /// 按月保存日志,物理表名后缀为 _yyyyMM。 [OrmTable(TableName = "logs", SplitType = SplitTableType.Month, SplitColumn = nameof(CreateTime))] public class Log { /// 每张物理表独立生成主键。 [OrmColumn(IsPrimaryKey = true, IsIdentity = true)] public long Id { get; set; } /// 日志内容。 [OrmColumn(IsNullable = NullableState.Nullable)] public string? Message { get; set; } /// 用于决定月份,分表依据列必须为 DateTime。 [OrmColumn(IsNullable = NullableState.Nullable)] public DateTime CreateTime { get; set; } } /// 按记录数保存采样,每个物理桶最多分配 10000 条插入记录。 [OrmTable(TableName = "sensor_data", MaxRowCount = 10000)] public class SensorData { /// 每张物理表独立生成主键。 [OrmColumn(IsPrimaryKey = true, IsIdentity = true)] public long Id { get; set; } /// 本次采样值。 [OrmColumn(IsNullable = NullableState.Nullable)] public double Value { get; set; } } /// 按月和记录数保存事件,物理表形如 events_202401_1。 [OrmTable(TableName = "events", SplitType = SplitTableType.Month, SplitColumn = nameof(EventTime), MaxRowCount = 50000)] public class Event { /// 每张物理表独立生成主键。 [OrmColumn(IsPrimaryKey = true, IsIdentity = true)] public long Id { get; set; } /// 决定事件所属的月份。 [OrmColumn(IsNullable = NullableState.Nullable)] public DateTime EventTime { get; set; } } ``` **分表操作示例:** 通过 `.Split(startTime, endTime)` 查询已有物理表,省略范围则查询全部分表;范围用于筛选分表,精确到行的时间条件仍需用 `Where` 指定。 ```csharp var startTime = DateTime.Now.AddDays(-30); var endTime = DateTime.Now; var list = await ormClient.Queryable() .Split(startTime, endTime) .Where(log => log.CreateTime >= startTime && log.CreateTime <= endTime) .ToListAsync(); ``` 实体增删改使用 `.SplitTable()`,更新时间分表时实体应携带原来的分表时间。行数分表按主键定位首张匹配物理表。返回单个自增标识的分表插入只接受一条实体。 ```csharp await ormClient.InsertableRange(logs).SplitTable().ExecuteAsync(); await ormClient.UpdatableRange(logs).SplitTable().ExecuteAsync(); await ormClient.DeletableRange(logs).SplitTable().ExecuteAsync(); await ormClient.BulkCopy().SplitTable().BulkInsertAsync(logs); await ormClient.BulkCopy().SplitTable().BulkUpdateAsync(logs); await ormClient.BulkCopy().SplitTable().BulkMergeAsync(logs); await ormClient.Savable(logs).SplitTable().ExecuteAsync(); ``` `Savable(...).SplitTable()` 按实际条件键定位已有记录,更新首张匹配表,并将新增记录交给相同的容量租约。行数实体路由按物理表名的 Ordinal 升序选择首个匹配桶,查询和分页目录按表名倒序读取。修改分表时间应显式调用 `MoveSplitTableAsync(original, replacement)`,传入原、新快照;完整主键必须相同且不能是自增键,删除与插入在同一事务内完成。条件更新通过 `SetColumns` 改分表时间会提前拒绝。 `Where(log => log.CreateTime >= startTime && log.CreateTime < endTime)` 中直接作用于时间分表列的合取比较可自动裁剪物理表,原始 WHERE 始终保留。OR、NOT、布尔比较包装、方法调用等复杂条件不推断范围,严格边界仍由数据库筛选行。 跨表 SQL 预览使用异步 `ToSqlAsync()`,实体、投影和聚合均返回基于实际目录的等价数据 SQL 与参数;同步 `ToSql()` 对跨表查询报错。预览不代表分页计数定位等全部执行命令。 ```csharp var query = ormClient.Queryable().Split() .Where(log => log.CreateTime >= startTime); var preview = await query.ToSqlAsync(); var firstPage = await query.ToCursorPageAsync(100); var secondPage = firstPage.HasMore ? await query.ToCursorPageAsync(100, firstPage.NextCursor) : null; ``` 游标分页不查询总数,按物理表名倒序、表内完整主键升序返回,每页仅多读取一行判断 `HasMore`。只支持完整实体、完整非空主键及参数化查询,不能混用自定义排序、投影、Distinct 或 Skip/Take。令牌属于相同逻辑表、连接配置和筛选参数;并发修改时不提供跨页快照。需要总数、页码或自定义排序时使用 `ToPageListAsync()`。 自动写入首次同步当前实体的物理表结构,后续热写入复用成功状态。需要主动同步列和索引时,显式调用 `CodeFirst.InitTableAsync(physicalTableName, EntityInfo.GetEntityInfo())`;同步只撤销该表的结构缓存,保留已发放的容量租约。物理表索引名由逻辑索引名和物理表名生成,各表独立维护唯一约束。实体查询的无排序 `FirstAsync()` 和从起点开始的有界 `Take()` 直接读取所需物理表,不预先统计全部历史表;需要总数的页码查询仍执行真实计数。 空分表目录的实体、投影及聚合查询均使用空集语义,`SumAsync()` 等可空标量返回 `null`;异步 SQL 预览也能直接执行。`BulkUpdateAsync()` 没有匹配记录时返回 0,不开启空数据事务;新增记录分类只用于合并。维护索引时,`DropIndexAsync(tableName, indexName)` 核对索引的物理表归属,避免误删邻表索引;SQLite 也隔离主库与同名临时索引。 默认每次查询真实分表目录,便于看见其它连接新建的表。表结构完全由当前进程管理时,可在创建客户端时设置 `ConnectionConfig.CacheSplitTableCatalog = true`,减少目录查询。经 TORM 维护 API 执行的 DDL 自动失效;外部 DDL、原始 SQL 或绕过分表路由的插入后,应在写入停止时调用 `ormClient.RefreshSplitTableMetadata()` 刷新目录、初始化及容量状态。 默认内置行数策略在同进程、相同完整连接配置的活动客户端之间共享容量预留,客户端副本也复用状态。SQLite 按驱动规则统一 `Data Source`、`DataSource` 和 `Filename` 等标准别名,保留日志与同步选项,其它配置相同的文件客户端共享容量。需要跨进程容量约束时,全部写入者启用 `ConnectionConfig.CoordinateSplitTableWrites = true` 并通过分表路由写入:MySQL 使用会话命名锁,PostgreSQL 使用会话建议锁,SQL Server 使用会话应用锁,SQLite 文件库使用数据库旁的稳定独占锁文件。协调锁覆盖表准备、数据事务和调用方外部事务完成,锁内重新读取目录及当前容量;该模式增加协调和实时统计成本,默认关闭。SQLite 锁文件在释放句柄后保留,存在文件不表示仍被锁定,不应在其它进程运行时删除。 服务器协调模式的外部事务必须使用 `ReadCommitted`;MySQL 在开始外部事务前还须预建物理表和索引。SQLite 外部事务若不能立即取得协调文件锁会明确失败,调用方须回滚后重试整个事务,避免其已持有的数据库锁与另一写入者形成反向等待;无外部事务的普通写入仍可取消等待。锁取得被取消、解锁失败或事务结果未知时,由各实现库淘汰物理会话,避免携锁连接返回池。正常锁等待超时不会释放其它会话的锁。该约束只覆盖全部遵守相同策略的 ORM 写入者,不涵盖旁路 SQL、不同容量配置及外部并发 DDL;SQLite 内存库不提供跨进程共享。 自定义 `IRowCountSplitTableService` 必须实现 `ReserveTablesAsync`,一次返回完整批次的 `IRowCountTableReservation`。每段须有非空表名、正条数和独立租约,条数之和等于请求数;分配失败由服务回收已发额度。执行器在写入前调用 `Enlist(ado)`,成功后以真实条数调用 `Complete(affected, ado)`,最后调用可重复的 `ReleasePending()`。已开始写入的额度归事务收尾,已知回滚归还,未知结果保守保留。自定义服务自行保证并发和容量策略;ORM 不再为其逐块追加 COUNT。显式 `NotifyInserted(tableName, count, ado)` 仍须按数据库归属处理,不能对同一租约重复通知。 采用内置数字桶名称规则的自定义服务可声明 `UsesNumericTableSuffix = true`,直接从一次真实目录中筛选时间与行数混合表;公共流程依据命名契约执行,不判断服务实现类型。其它命名保留默认值 `false`,由服务提供物理表列表。分表修改的可空 `WhereColumns` 将两个空值视为同一条件键,普通更新、Savable 与原生 Bulk 使用一致的比较规则;非空主键保留直接等号比较。 PostgreSQL 目录及维护操作使用连接的 `current_schema()`,SQL Server 使用登录用户的默认 schema。不同 schema 使用不同连接配置;不在活动客户端上切换搜索路径或默认 schema。逻辑表名按实现库的保守规则严格匹配,SQLite 忽略大小写,其余实现精确匹配;应始终使用物理名称的实际大小写。 一次分表增删改或 Bulk 调用的各物理表数据写入共用事务。已有外部事务时由调用方提交或回滚,调用失败后应回滚该事务;提交结果未知时保守保留容量。MySQL 外部事务开始前应预建所需表、列及索引,自动准备发现结构缺失会明确报错,避免 DDL 隐式提交。自动建表在自有数据事务之前执行,失败时已建的空表可能保留。MySQL 原生 Bulk 的重复键、截断等警告通过 `MySqlBulkLoadException` 抛出。 **分表类型说明:** | SplitTableType | 说明 | 表名后缀格式 | |----------------|------|-------------| | None | 不分表(默认) | - | | Day | 按日分表 | _yyyyMMdd | | Week | 按周分表 | _yyyyMMdd(周一日期) | | Month | 按月分表 | _yyyyMM | | Quarter | 按季度分表 | _yyyyQn | | Year | 按年分表 | _yyyy | #### 事务管理 ```csharp await ormClient.UseTranAsync(async () => { await ormClient.InsertableRange(addModels).ExecuteAsync().ConfigureAwait(false); }).ConfigureAwait(false); ``` #### AOP 日志拦截 ```csharp // SQL执行前日志 ormClient.Aop.OnLogExecuting = (sql, parameters) => { Console.WriteLine($"执行SQL: {sql}"); }; // SQL执行后日志 ormClient.Aop.OnLogExecuted = (sql, parameters, elapsed) => { Console.WriteLine($"SQL执行耗时: {elapsed.TotalMilliseconds}ms"); }; // 插入/更新前修改值 ormClient.Aop.OnDataExecuting = (args) => { if (args.DataExecutingType == DataExecutingType.Insert) { // 自动填充创建时间 if (args.Entity is BaseEntity entity) { entity.CreateTime = DateTime.Now; } } }; ``` Release 构建会生成 NuGet 包并输出到上级 `nupkgs` 目录。 ## 相关资源 - Docker 集成测试:[test/TORM.Test/README.docker-tests.md](test/TORM.Test/README.docker-tests.md) - 当前变更测试矩阵:[test/TORM.Test/TEST_COVERAGE_MATRIX.md](test/TORM.Test/TEST_COVERAGE_MATRIX.md) - 官方文档:https://runtime.thingsgateway.cn/ - Gitee:https://gitee.com/ThingsGateway/TORM - GitHub:https://github.com/kimdiego2098/ThingsGateway - QQ 群:605534569 ## 许可证 本项目使用 [Apache-2.0](LICENSE) 协议。