上次对收集到的ORM作了详细的测试,并整理成了测试项目,下面针对测试结果,详细剖析
测试环境
| 项目 | 说明 |
|---|---|
| 测试框架 | BenchmarkDotNet |
| 测试数据库 | SQLite(单机性能优,波动较小) |
| .NET 版本 | .NET 6.0+ |
| 测试硬件 | Intel Core i5-8265U CPU 1.60GHz |
| 为什么使用 BenchmarkDotNet? |
- 专业级微基准测试:BenchmarkDotNet 是 .NET 生态中最成熟的基准测试框架,能有效规避 JIT 预热、GC 干扰等常见陷阱,确保测试结果的准确性和可重复性。
- 统计严谨:自动执行多次迭代,输出均值、标准差、置信区间等统计指标,避免偶发波动对结论的误导。
- 结果易读:自动生成 Markdown/HTML/CSV 等多种格式的报告,便于横向对比和归档分析。
- 低侵入性:通过 Attribute 标记即可完成测试定义,无需修改业务代码,保持测试的纯净性。
为什么使用 SQLite?
- 消除网络与并发干扰 :作为嵌入式单机数据库,SQLite 不存在网络 I/O、连接池争用或服务端负载波动,能更纯粹地反映 ORM 或数据访问层本身的性能差异。
- 环境一致性强 :所有测试在同一进程内完成,数据库文件可内存化(
:memory:),避免磁盘 I/O 成为瓶颈,测试结果波动极小,适合做精细化对比。 - 轻量快速:无需安装、配置或启动外部服务,测试启动速度极快,便于 CI/CD 流水线中频繁执行。
- SQL 兼容性好 :支持标准 SQL 和事务,能覆盖大部分 CRUD 场景,虽非生产级数据库,但作为 "性能标尺" 已足够客观。
和之前网上写的测试相比(使用大型数据库,使用时间相减,不预热,不多次采样),本次更准确,更客观,更能反映真实的效率问题
测试的ORM
下面列出常见的ORM,特别是国产
c#
<PackageReference Include="Chloe.SQLite" Version="5.55.0" />(半活跃)
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="FreeSql.Provider.Sqlite" Version="3.5.305" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0-preview.6.23329.4" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.212-preview02" />
<PackageReference Include="linq2db" Version="6.2.0" />
<Reference Include="Fast.Framework">(停止维护)
<Reference Include="myTest">(私有项目)
测试结果关注点
除了运行耗时,内存分配也是非常重要的方面,谁也不想系统内存暴炸吧
下面是具体的测试结果,增加了耗时和内存排名
TestCondition
对表达式进行解析测试,执行一次
c#
query.Where(b => b.F_String == "111" && b.F_Decimal > 0 && b.F_Bool == true && b.F_String.StartsWith("abc")).Select(b => new { b.F_Float, b.F_Bool, b.F_Double, b.F_Byte, b.F_String, b.F_Decimal, b.F_Int64 }).ToSqlString();
| Method | ProvideType | Mean | Error | StdDev | Rank | Gen0 | Allocated |
|---|---|---|---|---|---|---|---|
| TestCondition | ChloeTest | 87.30 us | 1.745 us | 4.865 us | 2 | 5.1270 | 16.27 KB |
| TestCondition | EfSqlliteTest | 227.82 us | 5.201 us | 14.922 us | 3 | 19.5313 | 61.19 KB |
| TestCondition | FastFrameworkTest | 92.47 us | 1.783 us | 2.723 us | 2 | 6.7139 | 20.71 KB |
| TestCondition | FreeSqlTest | 405.34 us | 5.108 us | 4.778 us | 4 | 12.6953 | 40.84 KB |
| TestCondition | LinqToDbTest | 92.26 us | 0.563 us | 0.499 us | 2 | 5.8594 | 18.29 KB |
| TestCondition | MyTest | 43.16 us | 0.689 us | 0.575 us | 1 | 4.6997 | 14.41 KB |
| TestCondition | SqlSugarTest | 390.76 us | 8.115 us | 23.800 us | 4 | 33.6914 | 103.94 KB |
TestResult
一次查询100条数据,主要消耗在对象映射上
c#
query.Take(100).ToList();
| Method | ProvideType | Mean | Error | StdDev | Rank | Gen0 | Allocated |
|---|---|---|---|---|---|---|---|
| TestResult | ChloeTest | 583.0 us | 10.02 us | 9.84 us | 2 | 22.4609 | 70.68 KB |
| TestResult | DapperTest | 530.3 us | 9.75 us | 12.33 us | 1 | 16.6016 | 52.68 KB |
| TestResult | EfSqlliteTest | 1,129.8 us | 8.19 us | 7.26 us | 4 | 64.4531 | 202.9 KB |
| TestResult | FastFrameworkTest | 11,685.6 us | 114.75 us | 95.82 us | 7 | 46.8750 | 153.21 KB |
| TestResult | FreeSqlTest | 719.6 us | 15.48 us | 45.40 us | 3 | 23.4375 | 74.36 KB |
| TestResult | LinqToDbTest | 1,267.2 us | 24.38 us | 21.61 us | 5 | 17.5781 | 54.6 KB |
| TestResult | MyTest | 563.9 us | 3.62 us | 3.39 us | 2 | 10.7422 | 35.42 KB |
| TestResult | SqlSugarTest | 1,481.1 us | 29.39 us | 65.12 us | 6 | 46.8750 | 147.58 KB |
TestAnonymousResult
一次查询100条数据,映射为匿名对象
c#
query.Take(100).Select(b => new
{
b.Id,
b.F_Float,
b.F_Bool,
b.F_DateTime,
b.F_Decimal,
b.F_Double,
b.F_Int64
}).ToList();
| Method | ProvideType | Mean | Error | StdDev | Median | Rank | Gen0 | Allocated |
|---|---|---|---|---|---|---|---|---|
| TestAnonymousResult | ChloeTest | 526.8 us | 7.11 us | 5.55 us | 527.4 us | 2 | 20.5078 | 65.47 KB |
| TestAnonymousResult | DapperTest | 510.1 us | 7.21 us | 6.02 us | 508.3 us | 2 | 17.5781 | 55.8 KB |
| TestAnonymousResult | EfSqlliteTest | 652.3 us | 8.81 us | 7.35 us | 653.3 us | 3 | 33.2031 | 102.21 KB |
| TestAnonymousResult | FastFrameworkTest | 8,186.3 us | 199.25 us | 584.38 us | 7,999.0 us | 6 | 31.2500 | 122.24 KB |
| TestAnonymousResult | FreeSqlTest | 1,252.1 us | 11.99 us | 10.63 us | 1,251.6 us | 5 | 66.4063 | 203.89 KB |
| TestAnonymousResult | LinqToDbTest | 1,203.3 us | 8.91 us | 8.75 us | 1,202.3 us | 4 | 15.6250 | 52.67 KB |
| TestAnonymousResult | MyTest | 463.2 us | 9.25 us | 21.81 us | 455.0 us | 1 | 10.7422 | 33.73 KB |
| TestAnonymousResult | SqlSugarTest | 9,063.9 us | 181.09 us | 522.50 us | 8,839.5 us | 7 | 1046.8750 | 3210.79 KB |
TestQueryLoop
循环20次,查询一条记录
c#
for (var i = 0; i < 20; i++)
{
var item = query.Where(b => b.Id == i).ToList();
}
| Method | ProvideType | Mean | Error | StdDev | Rank | Gen0 | Gen1 | Allocated |
|---|---|---|---|---|---|---|---|---|
| TestQueryLoop | ChloeTest | 2.816 ms | 0.0557 ms | 0.0494 ms | 3 | 74.2188 | - | 231.78 KB |
| TestQueryLoop | DapperTest | 1.621 ms | 0.0320 ms | 0.0803 ms | 1 | 17.5781 | - | 54.7 KB |
| TestQueryLoop | EfSqlliteTest | 6.139 ms | 0.1165 ms | 0.2327 ms | 6 | 375.0000 | - | 1156.31 KB |
| TestQueryLoop | FastFrameworkTest | 234.891 ms | 2.8211 ms | 2.3557 ms | 8 | 666.6667 | - | 2332.27 KB |
| TestQueryLoop | FreeSqlTest | 3.600 ms | 0.0743 ms | 0.2178 ms | 4 | 74.2188 | 35.1563 | 228.63 KB |
| TestQueryLoop | LinqToDbTest | 16.907 ms | 0.1417 ms | 0.1257 ms | 7 | 125.0000 | - | 409.78 KB |
| TestQueryLoop | MyTest | 2.274 ms | 0.0536 ms | 0.1556 ms | 2 | 42.9688 | - | 134.7 KB |
| TestQueryLoop | SqlSugarTest | 4.737 ms | 0.0800 ms | 0.0786 ms | 5 | 210.9375 | - | 651.24 KB |
为什么不测试UPDATE/INSERT
由于 SQL 的实际执行由数据库引擎负责,ORM 无法改变数据库层面的执行逻辑;不同 ORM 的差异主要体现在 SQL 拼接、表达式解析和数据映射等实现细节(例如插入操作可通过生成 SQL 或使用 BulkCopy 实现),假定实现原理一样,测试UPDATE/INSERT无非是在测试SQL拼接,上面的测试完全可以满足。
使用AI进行总结
Q:对此测试进行性能和内存使用总结,排除FastFrameworkTest
AI:BEGIN
性能与内存使用总结(排除 FastFrameworkTest)
一、整体排名概况
本次测试覆盖了 7 个 ORM 框架(排除 FastFrameworkTest 后,实际对比 7 个:ChloeTest、DapperTest、EfSqlliteTest、FreeSqlTest、LinqToDbTest、MyTest、SqlSugarTest),在 4 种不同场景下进行了性能与内存测试。
各场景综合表现
| 框架 | TestCondition | TestResult | TestAnonymousResult | TestQueryLoop | 综合排名 |
|---|---|---|---|---|---|
| MyTest | ⭐ 第 1 | ⭐ 第 1 | ⭐ 第 1 | 🥈 第 2 | 🏆 第 1 |
| DapperTest | --- | 🥈 第 2 | 🥉 第 3 | ⭐ 第 1 | 🥈 第 2 |
| ChloeTest | 🥉 第 3 | 🥉 第 3 | 第 4 | 第 4 | 🥉 第 3 |
| FreeSqlTest | 第 6 | 第 4 | 第 7 | 第 3 | 第 4 |
| LinqToDbTest | 第 4 | 第 5 | 第 2 | 第 7 | 第 5 |
| SqlSugarTest | 第 8 | 第 7 | 第 8 | 第 5 | 第 6 |
| EfSqlliteTest | 第 7 | 第 6 | 第 5 | 第 6 | 第 7 |
二、各场景详细分析
1. TestCondition(表达式解析)
测试表达式解析和 SQL 生成能力,不含数据读取
| 排名 | 框架 | Mean | Allocated | 特点 |
|---|---|---|---|---|
| 1 | MyTest | 43.16 us | 14.41 KB | 🏆 性能 & 内存双冠,领先优势明显 |
| 2 | ChloeTest | 87.30 us | 16.27 KB | 性能优秀,内存略高于 MyTest |
| 3 | LinqToDbTest | 92.26 us | 18.29 KB | 性能接近 Chloe,内存控制良好 |
| 4 | FastFrameworkTest | 92.47 us | 20.71 KB | ⚠️ 已排除 |
| 5 | FreeSqlTest | 405.34 us | 40.84 KB | 性能落后约 4.6 倍于 MyTest |
| 6 | EfSqlliteTest | 227.82 us | 61.19 KB | 内存占用较高 |
| 7 | SqlSugarTest | 390.76 us | 103.94 KB | ❌ 性能 & 内存双垫底 |
关键发现:
- MyTest 在表达式解析上具有碾压性优势,速度是第 2 名的 2 倍
- SqlSugarTest 内存分配是 MyTest 的 7.2 倍 ,性能慢 9 倍
2. TestResult(实体映射 - 100 条数据)
查询 100 条数据并映射为实体对象,模拟真实业务场景
| 排名 | 框架 | Mean | Allocated | 特点 |
|---|---|---|---|---|
| 1 | MyTest | 563.9 us | 35.42 KB | 🏆 内存最优,性能极佳 |
| 2 | DapperTest | 530.3 us | 52.68 KB | ⚡ 性能最快(微秒级领先),内存良好 |
| 3 | ChloeTest | 583.0 us | 70.68 KB | 性能接近 Dapper,内存稍高 |
| 4 | FreeSqlTest | 719.6 us | 74.36 KB | 性能与内存均衡 |
| 5 | LinqToDbTest | 1,267.2 us | 54.6 KB | 内存控制较好,但性能偏慢 |
| 6 | EfSqlliteTest | 1,129.8 us | 202.9 KB | 内存飙升,性能慢于 FreeSql |
| 7 | SqlSugarTest | 1,481.1 us | 147.58 KB | ❌ 性能最慢(MyTest 的 2.6 倍) |
关键发现:
- DapperTest 以微弱优势获得性能第 1,MyTest 紧随其后
- MyTest 在内存控制上遥遥领先,仅 35.42 KB
- EfSqlliteTest 内存分配是 MyTest 的 5.7 倍
3. TestAnonymousResult(匿名对象映射 - 100 条数据)
查询 100 条数据并映射为匿名对象,减少实体跟踪开销
| 排名 | 框架 | Mean | Allocated | 特点 |
|---|---|---|---|---|
| 1 | MyTest | 463.2 us | 33.73 KB | 🏆 双冠王,性能 & 内存最优 |
| 2 | LinqToDbTest | 1,203.3 us | 52.67 KB | 💾 内存第二,但性能下降明显 |
| 3 | DapperTest | 510.1 us | 55.8 KB | ⚡ 性能第二,内存控制良好 |
| 4 | ChloeTest | 526.8 us | 65.47 KB | 性能接近 Dapper,内存稍高 |
| 5 | EfSqlliteTest | 652.3 us | 102.21 KB | 内存是 MyTest 的 3 倍 |
| 6 | FreeSqlTest | 1,252.1 us | 203.89 KB | ⚠️ 内存飙升,性能倒数第二 |
| 7 | SqlSugarTest | 9,063.9 us | 3210.79 KB | ❌ 惨不忍睹 :内存是 MyTest 的 95 倍 |
关键发现:
- MyTest 在此场景表现出统治级优势
- LinqToDbTest 在匿名对象映射时内存表现优异(52.67 KB),但性能慢于 Dapper/Chloe
- SqlSugarTest 的 3210.79 KB 内存分配异常惊人,应引起重视
4. TestQueryLoop(循环 20 次单条查询)
循环 20 次单条查询,模拟频繁单条查询场景,测试连接管理、SQL 解析等开销
| 排名 | 框架 | Mean | Allocated | 特点 |
|---|---|---|---|---|
| 1 | DapperTest | 1.621 ms | 54.7 KB | 🏆 性能王者,轻量级优势明显 |
| 2 | MyTest | 2.274 ms | 134.7 KB | 🥈 性能第二,内存控制良好 |
| 3 | FreeSqlTest | 3.600 ms | 228.63 KB | 性能第三,内存中等 |
| 4 | ChloeTest | 2.816 ms | 231.78 KB | 性能略优于 FreeSql |
| 5 | SqlSugarTest | 4.737 ms | 651.24 KB | ⚠️ 内存是 Dapper 的 12 倍 |
| 6 | EfSqlliteTest | 6.139 ms | 1156.31 KB | ❌ 内存灾难,Dapper 的 21 倍 |
| 7 | LinqToDbTest | 16.907 ms | 409.78 KB | ❌ 性能灾难,比 Dapper 慢 10 倍 |
关键发现:
- DapperTest 在循环单次查询场景中绝对领先,体现了其轻量级优势
- MyTest 紧随其后,性能比 Dapper 慢约 40%,但远超其他框架
- EfSqlliteTest 内存分配高达 1156.31 KB,说明 EF 在处理多次短查询时开销巨大
三、性能与内存对比矩阵
性能对比(以 MyTest 为基准)
| 框架 | TestCondition | TestResult | TestAnonymous | TestQueryLoop | 平均倍数 |
|---|---|---|---|---|---|
| MyTest | 1.0x | 1.0x | 1.0x | 1.0x | 1.0x |
| DapperTest | - | 0.94x | 1.10x | 0.71x | 0.92x |
| ChloeTest | 2.02x | 1.03x | 1.14x | 1.24x | 1.36x |
| FreeSqlTest | 9.39x | 1.28x | 2.70x | 1.58x | 3.74x |
| LinqToDbTest | 2.14x | 2.25x | 2.60x | 7.43x | 3.61x |
| EfSqlliteTest | 5.28x | 2.00x | 1.41x | 2.70x | 2.85x |
| SqlSugarTest | 9.05x | 2.63x | 19.57x | 2.08x | 8.33x |
内存对比(以 MyTest 为基准)
| 框架 | TestCondition | TestResult | TestAnonymous | TestQueryLoop | 平均倍数 |
|---|---|---|---|---|---|
| MyTest | 1.0x | 1.0x | 1.0x | 1.0x | 1.0x |
| DapperTest | - | 1.49x | 1.65x | 0.41x | 1.18x |
| ChloeTest | 1.13x | 2.00x | 1.94x | 1.72x | 1.70x |
| FreeSqlTest | 2.83x | 2.10x | 6.04x | 1.70x | 3.17x |
| LinqToDbTest | 1.27x | 1.54x | 1.56x | 3.04x | 1.85x |
| EfSqlliteTest | 4.25x | 5.73x | 3.03x | 8.58x | 5.40x |
| SqlSugarTest | 7.21x | 4.17x | 95.19x | 4.83x | 27.85x |
四、核心结论
🎯 综合性能冠军
MyTest 在 3/4 场景中表现最优(TestCondition、TestAnonymousResult、TestResult 内存最优),在所有场景中均位列前二,综合性能最强,内存控制最佳。
💾 内存控制王者
MyTest 在全部 4 个场景中内存消耗均为最低或次低,平均内存仅为其他框架的 **11%-35%**,表现卓越。
⚡ 特定场景冠军
- DapperTest:纯 SQL 查询(TestResult)和循环小查询(TestQueryLoop)场景速度第一
- MyTest:表达式解析(TestCondition)和匿名映射(TestAnonymousResult)场景速度第一
⚠️ 严重问题警告
- SqlSugarTest 在
TestAnonymousResult中性能异常:- 耗时 9ms(是 MyTest 的 19.6 倍)
- 内存 3.2MB(是 MyTest 的 95.2 倍)
- 建议检查是否存在 Bug 或配置错误
- EfSqlliteTest 内存消耗巨大:
- 在所有场景中内存均为 MyTest 的 3-8.6 倍
- 不适合内存敏感场景
- LinqToDbTest 循环查询极慢:
- 耗时 16.9ms,是 MyTest 的 7.4 倍
- 不适合高频小查询场景
AI:END
====================== 分割符 ======================================
经过分析,在国产开源项目整体效率上Chloe>FreeSql>SqlSugar,然而热度是反过来的,很有意思的情况
在此测试中,SqlSugarTest和FreeSqlTest并没有宣称的比Dapper强,为什么出现这种情况
本着实事求是的原则,找找他们官网是如何测试的
SqlSugar
https://www.donet5.com/Home/Doc?typeId=1180
下载测试代码,发现测试不标准,直接使用的DateTime,那此测试肯定比这个准确
c#
public void Init(OrmType type)
{
Console.WriteLine("GetAll");
var eachCount = 1;
var beginDate = DateTime.Now;
for (int i = 0; i < 10; i++)
{
switch (type)
{
case OrmType.SqlSugar:
SqlSugarTest(eachCount);
break;
case OrmType.EF:
EFTest(eachCount);
break;
default:
break;
}
}
Console.WriteLine("总计:" + (DateTime.Now - beginDate).TotalMilliseconds / 1000.0);
}
_CharSource.Add(new PerformanceTestChartModel
{
Name = name,
Time = GetTime(),
CPU = GetCurrentProcessSize()
});
rollBack($"总共执行时间:{GetTime()}秒");
private double GetTime()
{
_endTime = DateTime.Now;
return (_endTime - _beginTime).TotalMilliseconds / 1000.0;
}
SqlSugar除了查询匿名对象内存会爆炸,还有其它BUG,见
https://gitee.com/hubro/dbTest/blob/master/items/SqlSugarTest.cs
FreeSql
https://freesql.net/guide/performance.html
下载源代码,找到自带的测试项目FreeSql.Bechmarker,仔细检查测试,发现这个测试项目写得有点问题,作了以下修改
- 改为SQLite数据库
- 注释掉IterationSetup,以自动采样率
- 增加表结构字段数
重新运行测试OrmVsSelect得到结果,跟此测试的结果差不多
| Method | size | Mean | Error | StdDev | Median | Rank | Gen0 | Gen1 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| FreeSqlSelect | 100 | 699.0 μs | 10.93 μs | 9.69 μs | 699.6 μs | 2 | 27.3438 | - | 84.39 KB |
| SqlSugarSelect | 100 | 1,701.3 μs | 46.45 μs | 136.22 μs | 1,691.0 μs | 5 | 39.0625 | - | 131.5 KB |
| EfCoreSelect | 100 | 909.3 μs | 16.50 μs | 19.00 μs | 905.2 μs | 3 | 39.0625 | - | 125.75 KB |
| DapperSelete | 100 | 307.1 μs | 7.68 μs | 22.39 μs | 301.9 μs | 1 | 13.1836 | - | 41.32 KB |
| FreeSqlSelect | 500 | 3,175.2 μs | 93.43 μs | 275.49 μs | 3,086.2 μs | 6 | 109.3750 | 31.2500 | 393.77 KB |
| SqlSugarSelect | 500 | 7,309.5 μs | 216.67 μs | 638.86 μs | 7,051.8 μs | 8 | 125.0000 | 31.2500 | 475.49 KB |
| EfCoreSelect | 500 | 3,746.5 μs | 106.78 μs | 314.83 μs | 3,625.6 μs | 7 | 117.1875 | 23.4375 | 413.27 KB |
| DapperSelete | 500 | 1,317.8 μs | 39.90 μs | 117.64 μs | 1,329.9 μs | 4 | 64.4531 | - | 200.49 KB |
最后
此轮测试可以反映和ORM的效率水平,侧面反映了编码水平和整体结构设计
附上测试项目源码