dapper 的 orm 映射严格依赖列名与属性名逐字匹配(大小写敏感),不自动处理驼峰转换或特性映射;性能优化关键在于正确管理连接、参数绑定和结果物化时机。

直接说结论:Dapper 的 ORM 映射本身不“自动”做任何智能转换,它只靠列名和属性名严格匹配(大小写敏感)来赋值;所谓“快速提升性能”,本质是避开它最容易崩的几个点——连接状态、参数绑定、结果物化时机。
Query 返回 null 或全是默认值?先查列名和属性名是否完全一致
Dapper 不会帮你做驼峰转下划线、也不读 [Column] 或 [JsonProperty],它只看 SQL 返回的列名字符串是否和 C# 类属性名逐字相等(含大小写)。PostgreSQL 和 MySQL 默认区分大小写,SQL Server 虽常不敏感,但不能依赖。
- 最稳妥做法:SQL 里显式用别名对齐,比如
SELECT user_id AS UserId, created_at AS CreatedAt FROM users - 想全局统一映射?得手动调用
SqlMapper.SetTypeMap(),但 Dapper 自带的ColumnAttributeTypeMapper不识别[Column],你得自己写一个 - 别用
QuerySingleOrDefault<t>()</t>去查可能为空的结果——它在没数据时抛异常,而业务上更常见的是“查不到就返回 null”,这时该用QueryFirstOrDefault<t>()</t>
QueryAsync 报 “Invalid operation on a closed reader”?你没物化就返回了 IEnumerable
QueryAsync<t>()</t> 返回的是懒执行的 IEnumerable<t></t>,底层绑着还没关闭的 SqlDataReader。一旦 IDbConnection 在遍历前被释放(比如 using 块结束),再取数据就炸。
- Web API 中直接
return Ok(conn.QueryAsync<user>())</user>是错的——框架消费枚举时连接早关了 - 必须
await并立刻物化:var list = await conn.QueryAsync<user>(sql, param); return list.ToList();</user> - WinForms/WPF 中还要加
.ConfigureAwait(false)防同步上下文死锁,比如await conn.QueryAsync<user>(sql).ConfigureAwait(false)</user>
QueryMultiple 必须按顺序 Read,跳一个或漏一个就 InvalidOperationException
QueryMultiple 不是智能分表工具,它只是把一次响应中多个结果集按 SQL 语句顺序切开。你写 "SELECT Orders; SELECT OrderItems",就必须先 multi.Read<order>()</order>,再 multi.Read<orderitem>()</orderitem>。
- 中间插个
multi.Read<string>()</string>,或者只读第一个就丢掉第二个,立刻抛InvalidOperationException - 每个
Read<t>()</t>返回的是IEnumerable<t></t>,不是单个对象,要取全部记得.ToList() -
GridReader必须用using包裹,否则连接不会释放,容易连爆
事务不生效?Execute 和 Query 都得显式传 transaction 参数
Dapper 完全不感知连接上的事务上下文。哪怕你调了 conn.BeginTransaction(),所有 Execute() 和 Query() 默认都走默认连接,跟事务无关。
- 所有操作都得显式传参:
conn.Execute(sql, param, transaction: tx)和conn.Query(sql, param, transaction: tx) - 哪怕只是最后一条
SELECT校验中间状态,也得带上transaction: tx,否则看到的是未提交数据的快照外视图 - 别信“用了 using var tx = conn.BeginTransaction() 就自动绑定”,Dapper 没这层逻辑
真正卡住人的从来不是语法,而是 Dapper 把所有控制权交还给你之后,你忘了自己得管连接开没开、关没关,参数对没对,结果读没读完——它轻量,是因为它不兜底。











