dapper要求连接必须打开、列名严格匹配、参数名区分大小写;queryasync需await后.tolist()避免连接关闭异常;querymultiple须按sql顺序逐个read;gridreader需using确保释放。

Query<t></t> 必须确保连接已打开、列名严格匹配、参数用匿名对象传
Dapper 不做任何兜底,Query<t></t> 执行前连接必须是 Open 状态,否则直接抛异常或返回空;SQL 返回的列名(包括大小写)必须和目标类型 T 的属性名完全一致——PostgreSQL、MySQL 默认区分大小写,user_id 和 UserId 就是两个字段。
- 别依赖数据库的“不敏感”行为,SQL Server 也可能在某些排序规则下敏感
- 最稳妥做法:SQL 里显式用
AS别名对齐,比如SELECT user_id AS UserId, created_at AS CreatedAt FROM users - 不要给参数传
new { id = 123 }还写成WHERE @Id = @id,Dapper 参数名区分大小写,@id和@Id是不同参数 - 如果用
dynamic接收结果,列名仍需匹配,只是不用提前定义类型
QueryAsync<t></t> 的 await 链不能断,返回前必须物化
QueryAsync<t></t> 返回的是 Task<ienumerable>></ienumerable>,但底层是懒执行的:真正读取发生在枚举时。如果在 using 块里调用它,然后把未 await 的 Task 或未 .ToList() 的 IEnumerable<t></t> 丢出去,连接一释放,后续遍历就崩出 Invalid operation on a closed reader。
- Web API 中写
return Ok(conn.QueryAsync<user>(sql))</user>是典型错误,框架会在 action 返回后才消费枚举 - 正确姿势:要么
await conn.QueryAsync<user>(sql)</user>后立刻.ToList(),要么用ToListAsync()(需引用Dapper.Contrib或自己扩展) - 若真要流式响应(如大导出),必须保持连接生命周期与响应生命周期一致,通常得用
HttpResponseStream+ 手动yield return,且不能走默认 MVC 序列化
QueryMultiple 必须按 SQL 顺序逐个读,不能跳、不能漏
QueryMultiple 不是智能拆包器,它只是把一次 ExecuteReader 返回的多个结果集,按你 SQL 里 SELECT 出现的顺序切开。你写 SELECT users; SELECT orders; SELECT order_items,就必须依次调用 multi.Read<user>()</user> → multi.Read<order>()</order> → multi.Read<orderitem>()</orderitem>。
- 中间插一个
multi.Read<string>()</string>,或者少调一次Read,立刻抛InvalidOperationException: There is already an open DataReader或类似提示 - 每个
Read<t>()</t>返回的是IEnumerable<t></t>,不是单个对象;想取全部就得.ToList(),否则下次Read会失败 -
GridReader必须用using包裹,它不自动释放连接,漏掉就会连爆
真正容易被忽略的点是:Dapper 从不管理连接生命周期,也不改写你的 SQL。你写的什么,它就发什么;你关连接的时机,直接决定结果能不能拿到。所谓“轻量”,本质是把控制权全交还给你。










