Dapper是一个轻量级的.NET ORM框架,日常开发中我们最常用的是Query和Execute这些方法。但不少人会疑惑,Query和ExecuteReader到底是什么关系,Dapper底层又是怎么处理查询的。实际上Query是对ExecuteReader的一层封装,真正和数据库打交道读取结果集的,还是ADO.NET里的DataReader。

ExecuteReader与Query的基本定位
在Dapper中,ExecuteReader是一个比较偏底层的扩展方法,它直接返回一个IDataReader对象,把读取数据的控制权完全交给调用者。而Query则是更高层的API,它会内部调用ExecuteReader,然后把DataReader中的数据映射成强类型或动态对象列表再返回给你。
ExecuteReader的特点
- 返回的是原始的IDataReader,需要手动读取和关闭
- 适合需要流式处理、自定义读取逻辑的场景
- 性能上几乎没有额外封装开销
Query的特点
- 自动调用ExecuteReader并遍历结果
- 自动做字段到属性的映射
- 使用起来简单,但灵活性低于直接使用DataReader
Dapper底层是如何用ExecuteReader实现Query的
从Dapper的源码逻辑来看,Query方法最终会走到一个核心的执行函数,这个函数内部通过Command定义好SQL和参数后,调用数据库连接执行ExecuteReader,再利用反射或Emit生成的映射器把每一行转换成对象。
下面是一段简化后的示意代码,展示Query底层调用ExecuteReader的思路:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
public class SimpleDapperDemo
{
// 模拟Dapper中Query底层调用ExecuteReader的过程
public static List<T> Query<T>(SqlConnection conn, string sql)
{
var result = new List<T>();
using (var cmd = new SqlCommand(sql, conn))
{
// 底层实际调用ExecuteReader
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
// 简化映射:假设T只有一个Name属性
var obj = Activator.CreateInstance<T>();
var prop = typeof(T).GetProperty("Name");
if (prop != null)
{
prop.SetValue(obj, reader["Name"].ToString());
}
result.Add(obj);
}
}
}
return result;
}
}
可以看到,Query并没有绕开ExecuteReader,而是在它之上补全了结果集遍历和对象映射的工作。
什么时候该直接用ExecuteReader
虽然Query很方便,但在以下情况直接使用ExecuteReader更合适:
| 场景 | 推荐方式 |
|---|---|
| 超大数据量流式导出 | ExecuteReader |
| 需要边读边写文件 | ExecuteReader |
| 简单对象查询 | Query |
小结
总的来说,Dapper的Query是构建在ExecuteReader之上的高层封装,两者是调用与被调用的关系。理解了这一点,我们在做性能优化或特殊数据读取时,就能清楚是该继续用Query,还是退回到ExecuteReader自己控制流程。
DapperExecuteReaderQuery底层APIORM修改时间:2026-07-24 17:33:20