SqlReader读取存储过程结果集的存储位置及EF Core一次性读取方案
问题1:结果集存储位置
SqlDataReader的流式读取逻辑对应两段存储:
- 还未完成网络传输的剩余结果:暂存在SQL Server的输出缓冲区中,如果结果集超出SQL Server可分配的内存上限,会临时溢出到磁盘的TempDB中存储。
- 已经通过网络传输到应用端的结果:缓存在应用服务器内存的.NET SqlClient驱动内部缓冲区中,你每次调用
ReadAsync()都是从这个本地缓冲区中读取行数据,不需要每次都和SQL Server交互。
你观察到SQL Profiler只记录一次存储过程调用,是因为存储过程在SQL Server侧执行完成就会记录,和结果集是否完成全量传输没有关系。你的代码中最终会把所有读取到的行都添加到result列表中,整个结果集最后都会完整加载到应用服务器的托管内存中。
问题2:EF Core一次性读取完整结果集的方法
EF Core本身就提供了直接加载全量结果的封装,不需要手动操作DataReader:
方法1:使用原生SQL查询扩展(推荐)
你只需要提前在你的MyDBContext中将AvailableWorkDTO注册为无键实体:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<AvailableWorkDTO>().HasNoKey(); }
之后可以直接调用FromSqlInterpolated+ToListAsync一次性加载所有结果,EF Core内部会自动处理命令执行、结果读取、对象映射的全流程,CommandTimeout默认会使用你配置的上下文超时时间,也可以单独修改:
public static async Task<IEnumerable<AvailableWorkDTO>> prcGetAvailableWork(this MyDBContext dbContext, int userID) { // 单独设置本次查询的超时时间,单位秒 dbContext.Database.SetCommandTimeout(120); return await dbContext.Set<AvailableWorkDTO>() .FromSqlInterpolated($"EXEC dbo.prcGetAvailableWork @UserID={userID}") .ToListAsync(); }
方法2:EF Core 7.0+ 直接使用SqlQuery方法
如果你不想额外注册无键实体,EF Core 7及以上版本提供了非实体类型的原生查询支持:
public static async Task<IEnumerable<AvailableWorkDTO>> prcGetAvailableWork(this MyDBContext dbContext, int userID) { dbContext.Database.SetCommandTimeout(120); return await dbContext.Database .SqlQuery<AvailableWorkDTO>($"EXEC dbo.prcGetAvailableWork @UserID={userID}") .ToListAsync(); }
如果你还是要保留自己创建DbCommand的逻辑,要一次性读取所有结果,只需要把DataReader的结果直接加载到DataTable中即可:
using var reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false); var dataTable = new DataTable(); dataTable.Load(reader); // 再把DataTable转成对应的DTO列表即可
内容的提问来源于stack exchange,提问作者LP13
相关产品推荐
相关产品推荐

