# dbTest **Repository Path**: hubro/dbTest ## Basic Information - **Project Name**: dbTest - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-19 - **Last Updated**: 2026-03-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## ORM性能测试Benchmark(最终版) 本测试聚焦 ORM 在查询过程中,对查询表达式解析、数据映射、流程处理的性能差异。 由于 SQL 的实际执行由数据库引擎负责,ORM 无法改变数据库层面的执行逻辑;不同 ORM 的差异主要体现在 SQL 拼接、表达式解析和数据映射等实现细节(例如插入操作可通过生成 SQL 或使用 BulkCopy 实现)。 因此,本测试不对实现方式完全不同的操作(如 BulkCopy)进行比较,而是重点衡量表达式解析与数据映射两方面的运行效率与内存占用。 ## 测试声明 本测试不代表任何立场和原作者也没任何关系,仅是在研究、学习、优化、测试,对内部项目`myTest`整理过程中形成的测试,有其它测式可下载源码自行添加实现。 ## 测试环境 | 项目 | 说明 | | ------------ | --------------------------------- | | 测试框架 | BenchmarkDotNet | | 测试数据库 | SQLite(单机性能优,波动较小) | | .NET 版本 | .NET 6.0+ | | 测试硬件 | Intel Core i5-8265U CPU 1.60GHz | ## 测试的ORM 下面列出了近年收集到的ORM,除Dapper外,未涉及到表达式解析的ORM没有加入此测试 ```c# ``` ### 具体测试项目如下 1. 对表达式解析进行测试 testQueryCondition 2. 对查询返回结果进行数据映射测试 testQueryResult 3. 自定义数据映射测试 testQueryAnonymousResult 4. 循环读取指定数据测试 testQueryLoop ## 测试代码明细 ### 对表达式解析进行测试 testQueryCondition 指定了查询条件和返回结果筛选 这里可以反映表达式解析生成SQL效率,ORM技术核心最困难的部份,非常考验代码组织和逻辑 ```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(); ``` ### 对查询返回结果进行数据映射测试 testQueryResult 返回指定的数量的数据进行数据映射,以测试映射效率和内存使用情况 ```c# query.Take(100).ToList(); ``` ### 自定义数据映射测试 testQueryAnonymousResult 指定数据映射的结构,以测试解析和映射效率、内存使用情况 这里使用了匿名对象,是一个比较特殊的处理 ```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(); ``` ### 循环读取指定数据测试 testQueryLoop 循环多次查询,以测试查询和映射效率 这里验证数据连接效率和查询效率,虽然每次查询的数据量很小,但循环多次会放大差异,能更明显的看出差别 ```c# for (var i = 0; i < 20; i++) { var item = query.Where(b => b.Id == i).ToList(); } ``` Benchmark测试代码样例 ```c# [MemoryDiagnoser] public class ConditionTest : TestBase { [Benchmark] public void TestCondition() { Invoke(b => b.testQueryCondition()); } } ``` ## 以下是具体测试结果,仅供参考 > 表格字段说明: * Mean: 所有测量值的算术平均值 ns纳秒 μs微秒 ms毫秒。 * Error: 99.9% 置信区间的一半。 * StdDev: 所有测量值的标准差。 * Gen0: 第 0 代 GC 每 1000 次操作收集一次。 * Gen1: 第 1 代 GC 每 1000 次操作收集一次。 * Gen2: 第 2 代 GC 每 1000 次操作收集一次。 * Allocated: 每次操作分配的内存(仅托管内存,包含所有内容,1KB = 1024B)。 ### TestCondition Dapper由于没有表达式解析,空跑,最低和最高不管是效率和内存占用差了一数量级 **提示**:拼接SQL会导致注入风险,除了语法字符外还有字符编码问题 | Method | ProvideType | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | | --------------- | ------------------- | ------------ | ------------ | ------------ | --------- | -------- | ----------- | | TestCondition | ChloeTest | 90.75 μs | 1.020 μs | 0.954 μs | 5.3711 | - | 16.68 KB | | TestCondition | EfSqlliteTest | 217.38 μs | 2.950 μs | 2.615 μs | 19.5313 | - | 61.19 KB | | TestCondition | FastFrameworkTest | 94.90 μs | 0.807 μs | 0.630 μs | 6.7139 | - | 20.71 KB | | TestCondition | FreeSqlTest | 779.56 μs | 12.514 μs | 11.706 μs | 9.7656 | 3.9063 | 62.56 KB | | TestCondition | LinqToDbTest | 103.50 μs | 2.002 μs | 2.056 μs | 6.1035 | - | 18.79 KB | | TestCondition | MyTest | 43.00 μs | 0.505 μs | 0.395 μs | 4.8218 | - | 14.95 KB | | TestCondition | SqlSugarTest | 366.82 μs | 7.205 μs | 6.016 μs | 33.6914 | - | 103.83 KB | ### TestResult 强类型直接转换,最低和最高差别两倍,EF效率比大部份好 | Method | ProvideType | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | | ------------ | ------------------- | -------------- | ------------ | ----------- | --------- | --------- | ----------- | | TestResult | ChloeTest | 582.8 μs | 10.02 μs | 8.37 μs | 22.4609 | - | 70.68 KB | | TestResult | DapperTest | 500.8 μs | 3.14 μs | 2.94 μs | 16.6016 | - | 52.68 KB | | TestResult | EfSqlliteTest | 1,127.4 μs | 15.77 μs | 14.75 μs | 64.4531 | - | 202.9 KB | | TestResult | FastFrameworkTest | 11,561.9 μs | 119.70 μs | 93.46 μs | 46.8750 | - | 153.21 KB | | TestResult | FreeSqlTest | 1,300.0 μs | 24.25 μs | 22.68 μs | 31.2500 | 11.7188 | 96.32 KB | | TestResult | LinqToDbTest | 1,273.8 μs | 20.71 μs | 18.36 μs | 17.5781 | - | 54.77 KB | | TestResult | MyTest | 600.5 μs | 11.94 μs | 11.16 μs | 15.6250 | - | 48.34 KB | | TestResult | SqlSugarTest | 1,408.5 μs | 27.42 μs | 38.44 μs | 46.8750 | - | 147.57 KB | ### TestAnonymousResult Dapper由于没有结果筛选,同TestResult(SqlSugar内存溢出?) | Method | ProvideType | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | | --------------------- | ------------------- | ------------- | ------------ | ------------ | ----------- | --------- | ------------ | | TestAnonymousResult | ChloeTest | 552.7 μs | 5.48 μs | 4.86 μs | 20.5078 | - | 65.54 KB | | TestAnonymousResult | DapperTest | 484.2 μs | 4.71 μs | 4.18 μs | 17.5781 | - | 55.8 KB | | TestAnonymousResult | EfSqlliteTest | 692.2 μs | 11.48 μs | 10.74 μs | 33.2031 | - | 103.18 KB | | TestAnonymousResult | FastFrameworkTest | 7,838.4 μs | 67.26 μs | 52.51 μs | 31.2500 | - | 122.4 KB | | TestAnonymousResult | FreeSqlTest | 1,940.9 μs | 20.24 μs | 18.93 μs | 70.3125 | 11.7188 | 225.84 KB | | TestAnonymousResult | LinqToDbTest | 1,241.1 μs | 18.58 μs | 14.51 μs | 15.6250 | - | 53.21 KB | | TestAnonymousResult | MyTest | 473.4 μs | 4.67 μs | 4.14 μs | 12.6953 | - | 39.89 KB | | TestAnonymousResult | SqlSugarTest | 8,667.7 μs | 133.08 μs | 111.13 μs | 1046.8750 | - | 3210.65 KB | ### TestQueryLoop 循环多次查询调用,由于调用sqlite驱动不同,时间差别较大,但从内存使用上能看出差别 | Method | ProvideType | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | | --------------- | ------------------- | ------------ | ----------- | ----------- | ---------- | --------- | ------------ | | TestQueryLoop | ChloeTest | 2.535 ms | 0.0427 ms | 0.0379 ms | 74.2188 | - | 231.78 KB | | TestQueryLoop | DapperTest | 1.433 ms | 0.0274 ms | 0.0243 ms | 17.5781 | - | 54.7 KB | | TestQueryLoop | EfSqlliteTest | 5.410 ms | 0.0697 ms | 0.0618 ms | 375.0000 | - | 1156.31 KB | | TestQueryLoop | FastFrameworkTest | 230.424 ms | 4.5239 ms | 4.0103 ms | 666.6667 | - | 2332.27 KB | | TestQueryLoop | FreeSqlTest | 16.176 ms | 0.3208 ms | 0.6257 ms | 93.7500 | 31.2500 | 664.93 KB | | TestQueryLoop | LinqToDbTest | 16.881 ms | 0.2541 ms | 0.2252 ms | 125.0000 | - | 406.34 KB | | TestQueryLoop | MyTest | 2.058 ms | 0.0405 ms | 0.0379 ms | 46.8750 | - | 148.13 KB | | TestQueryLoop | SqlSugarTest | 4.210 ms | 0.0739 ms | 0.0655 ms | 210.9375 | - | 651.17 KB | ## 引入不同数据库实现的方式 本次测试使用的sqlite数据库,各种引入的方式也不相司,大致分为三类: 1. 引用相关数据库的项目扩展包(ORM主体+扩展+数据库驱动) 2. 直接包含所有支持的数据库驱动(ORM主体+数据库驱动*N) 3. 按需自动或手动配置引入的数据库驱动(ORM主体+按需数据库驱动) 对于第一种,多数项目都采用这种方式,缺点是需要引入多个包,增加了依赖关系,并且强绑定了数据库驱动 对于第二种,虽然可以支持多种数据库,但会增加项目的体积,并且可能引入不必要的依赖,也强绑定了数据库驱动 对于第三种,封装度最高,虽然可以按需引入数据库驱动,减少了项目的体积和依赖复杂度,但由于没有强依赖,部份实现可能由于驱动不一致而实现复杂,如 BulkCopy 以LinqToDb为例,直接引入LinqToDb包,手动配置连接串 new DataOptions().UseConnectionString(ProviderName.SQLite, ITest.sqlLiteDb) 它就能自别识别当前项目引入的sqlite驱动,并且对特殊方法也进行了封装(BulkCopy),不管是哪种数据库,引入驱动后所有方法行为一至,无其它依赖。 各种测试项目的引入方式如下表所示: | ProvideType | 引入方式 | | ----------------- | ----------- | | ChloeTest | 扩展包&手动配置 | | DapperTest | 扩展包 | | EfSqlliteTest | 扩展包 | | FastFrameworkTest | 手动配置 | | FreeSqlTest | 扩展包 | | MyTest | 手动或自动配置| | SqlSugarTest | 全包含 | | LinqToDbTest | 自动配置 | ### 如何使用此测试 1. 使用Release发布此项目 2. 运行dbTest.exe 3. 输入序号选择需要运行测试的方法,示例如下 ```cs ---------------------[ Program ]--------------------- [1] testQueryResult [2] testQueryAnonymousResult [3] testQueryCondition [4] testQueryLoop [5] testMethod ---------------------[ invokeAll ]--------------------- [6] invokeAll invoke method: ``` 4. 输入序号后回车,等待测试完成,测试结果会输出到控制台,并在运行目录生成BenchmarkDotNet.Artifacts文件夹,里面有详细的测试结果和分析报告 5. 运行效果截图 ![dbTest.gif](http://openwrite.cn/uploads/59/55589/cc51a36d-1dad-41f1-bad0-ba56974b420e.gif) ### 测试项目代码 https://gitee.com/hubro/dbTest.git ## 参考 使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试 https://cloud.tencent.com/developer/article/2483382 mysql注入-字符编码技巧 https://developer.aliyun.com/article/1658273