TORM 2.0.90
dotnet add package TORM --version 2.0.90
NuGet\Install-Package TORM -Version 2.0.90
<PackageReference Include="TORM" Version="2.0.90" />
<PackageVersion Include="TORM" Version="2.0.90" />
<PackageReference Include="TORM" />
paket add TORM --version 2.0.90
#r "nuget: TORM, 2.0.90"
#:package TORM@2.0.90
#addin nuget:?package=TORM&version=2.0.90
#tool nuget:?package=TORM&version=2.0.90
<p align="center"> <img src="logo.svg" width = "400" height = "200" alt="TORM Logo"/> </p>
<h1 align="center">TORM - 高性能 .NET ORM 框架</h1>
<div align="center">
轻量级、高性能、功能丰富的对象关系映射框架
TGateway 生态系统中的 ORM 组件,专为高性能场景设计。
</div>
中文
📖 项目简介
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<T>();需要完整 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,部分拒绝和全部拒绝都会抛出包含数量与原始响应的异常。
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<SensorRow>();
var writer = new QuestDbCsvBulkWriter<SensorRow>(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;同一个客户端不会在两种模型之间自动探测或失败回退。
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<SensorPoint>("device_1001");
var writer = client.CreateTreeWriter<SensorPoint>("device_1001");
writer.BatchSize = 500;
await writer.InsertAsync(points);
Table 模型使用 IoTdbTableSchema、TableCodeFirst 和 CreateTableWriter<T>();Tree 模型使用 device path、measurement、TreeCodeFirst 和 CreateTreeWriter<T>()。两种模型的查询都返回 IoTdbQueryResult,时间以 UTC 毫秒进入原生协议。
TDengine
TDengine 连接必须显式选择 Protocol=Native 或 Protocol=WebSocket。当前客户端直接管理 transport 和结果生命周期,不再提供 DbConnection、DbCommand、DbDataReader 或 TaosDataOrmClient 兼容外壳。
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是一个没有任何历史包袱的项目,其高性能设计和批量操作优化,在大数据量场景下表现尤为突出。
注:实际性能因数据库类型、数据量、硬件配置等因素会有所不同。建议在实际项目环境中进行测试。
项目结构
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/ 自动化测试
构建与测试
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。
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 注意事项见 数据库集成测试说明,当前生产变更与测试名称的逐项映射见 测试覆盖矩阵。
2026-08-30 的完整 Release 实测结果为 3808/3808 通过、0 失败、0 跳过;测试覆盖四种关系型 provider 的真实 CRUD/事务/分表/索引/BulkCopy,以及 QuestDB、TDengine、IoTDB 的真实原生协议闭环。
运行基准测试:
dotnet run -c Release --project benchmark/TORM.Benchmark/TORM.Benchmark.csproj
基础配置
using TORM;
// 创建 ORM 客户端
using var ormClient = new OrmClient(new OrmConnectionConfig
{
DatabaseType = OrmDbType.MySql,
ConnectionString = "Server=localhost;Database=test;Uid=root;Pwd=password;"
});
定义实体
[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 自动建表
// 自动创建表结构
await ormClient.CodeFirst.InitTableAsync<User>();
📚 功能详解
查询操作
// 基础查询
var list = await ormClient.Queryable<User>().ToListAsync();
// 条件查询
var adults = await ormClient.Queryable<User>()
.Where(u => u.Age >= 18)
.ToListAsync();
// 分页查询(返回 PagedList<T>,包含总数)
var page = await ormClient.Queryable<User>()
.Where(u => u.IsActive)
.OrderByDescending(u => u.CreateTime)
.ToPageListAsync(pageNumber: 1, pageSize: 20);
// 聚合查询
var count = await ormClient.Queryable<User>().CountAsync();
var exists = await ormClient.Queryable<User>().AnyAsync();
// 投影查询
var names = await ormClient.Queryable<User>()
.Select(u => new { u.Id, u.Name })
.ToListAsync();
// 流式查询(大数据量场景)
await foreach (var user in ormClient.Queryable<User>().ToAsyncEnumerable())
{
// 逐行处理
}
批量操作
// 批量插入(高性能)
await ormClient.InsertableRange(users).ExecuteAsync();
// BulkCopy 批量导入(最高性能)
await ormClient.BulkCopy<User>().BulkInsertAsync(users);
// 批量更新
await ormClient.BulkCopy<User>().BulkUpdateAsync(users);
// 批量保存(自动判断插入/更新)
await ormClient.BulkCopy<User>().BulkMergeAsync(users);
分表操作
定义分表实体:
/// <summary>按月保存日志,物理表名后缀为 _yyyyMM。</summary>
[OrmTable(TableName = "logs", SplitType = SplitTableType.Month, SplitColumn = nameof(CreateTime))]
public class Log
{
/// <summary>每张物理表独立生成主键。</summary>
[OrmColumn(IsPrimaryKey = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>日志内容。</summary>
[OrmColumn(IsNullable = NullableState.Nullable)]
public string? Message { get; set; }
/// <summary>用于决定月份,分表依据列必须为 DateTime。</summary>
[OrmColumn(IsNullable = NullableState.Nullable)]
public DateTime CreateTime { get; set; }
}
/// <summary>按记录数保存采样,每个物理桶最多分配 10000 条插入记录。</summary>
[OrmTable(TableName = "sensor_data", MaxRowCount = 10000)]
public class SensorData
{
/// <summary>每张物理表独立生成主键。</summary>
[OrmColumn(IsPrimaryKey = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>本次采样值。</summary>
[OrmColumn(IsNullable = NullableState.Nullable)]
public double Value { get; set; }
}
/// <summary>按月和记录数保存事件,物理表形如 events_202401_1。</summary>
[OrmTable(TableName = "events",
SplitType = SplitTableType.Month,
SplitColumn = nameof(EventTime),
MaxRowCount = 50000)]
public class Event
{
/// <summary>每张物理表独立生成主键。</summary>
[OrmColumn(IsPrimaryKey = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>决定事件所属的月份。</summary>
[OrmColumn(IsNullable = NullableState.Nullable)]
public DateTime EventTime { get; set; }
}
分表操作示例:
通过 .Split(startTime, endTime) 查询已有物理表,省略范围则查询全部分表;范围用于筛选分表,精确到行的时间条件仍需用 Where 指定。
var startTime = DateTime.Now.AddDays(-30);
var endTime = DateTime.Now;
var list = await ormClient.Queryable<Log>()
.Split(startTime, endTime)
.Where(log => log.CreateTime >= startTime && log.CreateTime <= endTime)
.ToListAsync();
实体增删改使用 .SplitTable(),更新时间分表时实体应携带原来的分表时间。行数分表按主键定位首张匹配物理表。返回单个自增标识的分表插入只接受一条实体。
await ormClient.InsertableRange(logs).SplitTable().ExecuteAsync();
await ormClient.UpdatableRange(logs).SplitTable().ExecuteAsync();
await ormClient.DeletableRange(logs).SplitTable().ExecuteAsync();
await ormClient.BulkCopy<Log>().SplitTable().BulkInsertAsync(logs);
await ormClient.BulkCopy<Log>().SplitTable().BulkUpdateAsync(logs);
await ormClient.BulkCopy<Log>().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() 对跨表查询报错。预览不代表分页计数定位等全部执行命令。
var query = ormClient.Queryable<Log>().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<Log>());同步只撤销该表的结构缓存,保留已发放的容量租约。物理表索引名由逻辑索引名和物理表名生成,各表独立维护唯一约束。实体查询的无排序 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 |
事务管理
await ormClient.UseTranAsync(async () =>
{
await ormClient.InsertableRange(addModels).ExecuteAsync().ConfigureAwait(false);
}).ConfigureAwait(false);
AOP 日志拦截
// 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/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 协议。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
NuGet packages (8)
Showing the top 5 NuGet packages that depend on TORM:
| Package | Downloads |
|---|---|
|
TORM.Sqlite
TORM.Sqlite 是 TORM 高性能 ORM 框架的 SQLite 驱动包,提供 SQLite 数据库的完整支持,包含查询、插入、更新、删除、CodeFirst 及批量操作等功能。 |
|
|
ThingsGatewayRuntime.Admin
Package Description |
|
|
TORM.TaosData
TORM.TaosData 是 Taos 驱动包。 |
|
|
TORM.MySql
TORM.MySql 是 TORM 高性能 ORM 框架的 MySQL 驱动包,提供 MySQL/MariaDB 数据库的完整支持,包含查询、插入、更新、删除、CodeFirst 及批量操作等功能。 |
|
|
TORM.PostgreSql
TORM.PostgreSql 是 TORM 高性能 ORM 框架的 PostgreSQL 驱动包,提供 PostgreSQL 数据库的完整支持,包含查询、插入、更新、删除、CodeFirst 及批量操作等功能。 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.90 | 0 | 11/1/2026 |
| 2.0.87 | 130 | 10/28/2026 |
| 2.0.86 | 246 | 10/26/2026 |
| 2.0.85 | 243 | 10/26/2026 |
| 2.0.84 | 104 | 10/26/2026 |
| 2.0.83 | 249 | 10/23/2026 |
| 2.0.82 | 422 | 10/19/2026 |
| 2.0.81 | 245 | 10/14/2026 |
| 2.0.80 | 238 | 10/13/2026 |
| 2.0.79 | 236 | 10/12/2026 |
| 2.0.78 | 369 | 10/7/2026 |
| 2.0.77 | 307 | 9/30/2026 |
| 2.0.76 | 237 | 9/29/2026 |
| 2.0.75 | 340 | 9/23/2026 |
| 2.0.74 | 270 | 9/21/2026 |
| 2.0.73 | 254 | 9/18/2026 |
| 2.0.72 | 296 | 9/14/2026 |
| 2.0.71 | 398 | 9/2/2026 |
| 2.0.69 | 299 | 8/31/2026 |
| 2.0.68 | 257 | 8/30/2026 |