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<t>()</t>是按 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<User>().ToList();
var orders = reader.Read<Order>().ToList(); // 必须在 Read<User> 之后常见错误:
先调Read<order>()</order>再
Read<user>()</user>→ 第二个读取返回空集合 漏掉某个
Read<t>()</t>调用 → 后续结果集无法访问(
GridReader不支持跳过) 对同一结果集重复调用
Read<t>()</t>→ 第二次返回空(数据已读完)
参数传递和 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<t>()</t>仍会返回空集合,不会跳过或报错——这意味着你必须依赖业务逻辑判断“空是否合法”,而不是靠异常来发现查询异常。
