必须用 using 包裹 QueryMultiple,因其返回的 GridReader 持有未关闭数据库连接,不及时释放易致连接池耗尽;且 Read() 必须严格按 SQL 结果集顺序调用,否则错位或抛异常。

QueryMultiple 为什么必须用 using 包裹
Dapper 的 QueryMultiple 返回的是 GridReader 对象,它底层持有一个未关闭的数据库连接。如果不显式释放,连接会一直占用直到 GC 回收(甚至可能不回收),极易触发连接池耗尽错误,比如 Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool.。
实操建议:
- 必须用
using语句包裹QueryMultiple调用,确保GridReader被及时Dispose() - 不要把它赋值给类字段或长期持有;不能在
using外访问GridReader - 如果需异步执行,改用
QueryMultipleAsync并配合await using
如何正确读取多个结果集(Read 顺序不能错)
GridReader.Read 是按 SQL 中查询语句出现顺序依次读取的,不是按类型自动匹配。一旦调用顺序与 SQL 结果集顺序不一致,后续读取会全部错位或抛出 InvalidOperationException: The IDataReader has been disposed.(因为内部指针已移到末尾)。
示例 SQL(两个结果集):
SELECT Id, Name FROM Users WHERE Active = 1; SELECT OrderId, Total FROM Orders WHERE UserId = @id;
对应 C# 代码必须严格按此顺序读取:
using var reader = connection.QueryMultiple(sql, new { id = 123 });
var users = reader.Read().ToList();
var orders = reader.Read().ToList(); // 必须在 Read 之后 常见错误:
- 先调
Read再() Read→ 第二个读取返回空集合() - 漏掉某个
Read调用 → 后续结果集无法访问(() GridReader不支持跳过) - 对同一结果集重复调用
Read→ 第二次返回空(数据已读完)()
参数传递和 SQL 拼接注意事项
QueryMultiple 的参数只绑定一次,作用于整个多语句 SQL 字符串。所有分号分隔的查询共享同一组参数,不能为每个子查询单独传参。
关键限制:
- SQL 中所有查询语句必须使用相同参数名,例如都用
@id,不能一个用@userId、另一个用@uid - 不支持动态拼接不同参数的多语句(如根据条件决定是否追加第二个查询);需提前构造完整 SQL 字符串
- SQL Server 允许用分号分隔;MySQL 需开启
Allow User Variables=true且慎用多语句(默认禁用),PostgreSQL 不支持原生多结果集,需用UNION ALL或函数封装替代
安全建议:避免字符串拼接参数,始终用命名参数,防止注入。例如不要写 "SELECT * FROM Users WHERE Id = " + id。
性能与映射边界问题
QueryMultiple 本质是单次往返(round-trip)执行多个查询,比多次 Query 节省网络开销,但所有结果集会同时加载进内存。当任一结果集极大时,容易引发 OutOfMemoryException,尤其在 Web 应用中。
优化方向:
- 对大数据量结果集,优先考虑分页或流式处理(但
GridReader不支持流式,只能靠 SQL 限行) - 避免在单个
QueryMultiple中混入高延迟查询(如带复杂 JOIN 的报表 + 快速查配置表),慢查询会拖累全部 - 映射时若某结果集列名与目标类型属性不匹配,Dapper 默认忽略该列(不报错),容易静默丢数据;建议开启
Settings.UseAutoSelect = true或手动检查SqlMapper.GridReader的列元数据
最易被忽略的一点:SQL Server 中,如果任意一个结果集为空(如 SELECT * FROM Table WHERE 1=0),Read 仍会返回空集合,不会跳过或报错——这意味着你必须依赖业务逻辑判断“空是否合法”,而不是靠异常来发现查询异常。










