C# Web API读取大量数据内存溢出问题及DataSet等方案咨询
问题描述
SQL Server数据库存储了大量数据,已开发Web API读取记录。使用SqlDataReader读取全部数据时,在Google Chrome测试出现内存溢出问题。希望使用DataSet处理这些数据,请问:
- 如何将DataSet与SqlDataReader结合使用?
- 是否需要改用DataTable配合DataSet?
- 还有哪些处理大量数据的可行方案?
现有代码如下:
Web API接口代码
public IHttpActionResult Get() { List<TestClass> draft = new List<TestClass>(); string mainconn = ConfigurationManager.ConnectionStrings["myconn"].ConnectionString; SqlConnection sqlconn = new SqlConnection(mainconn); string sqlquery = "SELECT UserID, Name, Mobile, Age, Date From tbluser"; sqlconn.Open(); SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn); SqlDataReader sdr = sqlcomm.ExecuteReader(); while (sdr.Read()) { draft.Add(new TestClass() { UserId = sdr.GetString(0), Name = sdr.IsDBNull(1) ? string.Empty : sdr.GetString(1), Mobile = sdr.IsDBNull(2) ? string.Empty : sdr.GetString(2), Age = (sdr.GetValue(3) != DBNull.Value) ? Convert.ToInt32(sdr.GetValue(3)) : 0, Date = (sdr.GetValue(4) != DBNull.Value) ? Convert.ToDateTime(sdr.GetValue(4)) : (DateTime?)null }); } return Ok(draft); }
TestClass类定义
public class TestClass { public string UserId { get; set; } public string Name { get; set; } public string Mobile { get; set; } public int Age { get; set; } public DateTime? Date { get; set; } }
一、DataSet与SqlDataReader的结合方式及DataTable的作用
DataSet本质是DataTable的集合,要结合SqlDataReader使用,必须先将数据加载到DataTable,再把DataTable加入DataSet——直接用SqlDataReader填充DataSet的底层逻辑也是如此。
具体实现代码
public IHttpActionResult Get() { DataSet dataSet = new DataSet(); string mainconn = ConfigurationManager.ConnectionStrings["myconn"].ConnectionString; using (SqlConnection sqlconn = new SqlConnection(mainconn)) { string sqlquery = "SELECT UserID, Name, Mobile, Age, Date From tbluser"; SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn); sqlconn.Open(); // 用SqlDataReader填充DataTable DataTable dataTable = new DataTable(); dataTable.Load(sqlcomm.ExecuteReader()); // 将DataTable加入DataSet dataSet.Tables.Add(dataTable); } // 注意:若将DataTable转成TestClass列表,依然会把所有数据加载到内存,大数据量下不建议这么做 return Ok(dataSet); }
⚠️ 重要提示:DataSet/DataTable本质还是把所有数据加载到内存,和你原来用List<TestClass>的问题完全一样——数据量极大时,依然会触发内存溢出,这不是根本解决方案。
二、处理大量数据的可行方案
1. 分页查询(最常用)
通过SQL的OFFSET和FETCH NEXT实现分页,API接收页码和每页条数参数,每次只返回部分数据,从根源减少内存占用。
public IHttpActionResult Get(int pageIndex = 1, int pageSize = 100) { List<TestClass> draft = new List<TestClass>(); string mainconn = ConfigurationManager.ConnectionStrings["myconn"].ConnectionString; using (SqlConnection sqlconn = new SqlConnection(mainconn)) { // 分页SQL,必须指定排序字段保证分页一致性 string sqlquery = @"SELECT UserID, Name, Mobile, Age, Date FROM tbluser ORDER BY UserID OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY"; SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn); sqlcomm.Parameters.AddWithValue("@Offset", (pageIndex - 1) * pageSize); sqlcomm.Parameters.AddWithValue("@PageSize", pageSize); sqlconn.Open(); using (SqlDataReader sdr = sqlcomm.ExecuteReader()) { while (sdr.Read()) { draft.Add(new TestClass() { UserId = sdr.GetString(0), Name = sdr.IsDBNull(1) ? string.Empty : sdr.GetString(1), Mobile = sdr.IsDBNull(2) ? string.Empty : sdr.GetString(2), Age = (sdr.GetValue(3) != DBNull.Value) ? Convert.ToInt32(sdr.GetValue(3)) : 0, Date = (sdr.GetValue(4) != DBNull.Value) ? Convert.ToDateTime(sdr.GetValue(4)) : (DateTime?)null }); } } } return Ok(new { Data = draft, PageIndex = pageIndex, PageSize = pageSize }); }
2. 流式返回数据(避免一次性加载到内存)
使用ASP.NET Web API的流式响应,直接通过SqlDataReader逐行写入响应流,不需要把所有数据存到内存集合中。
public HttpResponseMessage Get() { string mainconn = ConfigurationManager.ConnectionStrings["myconn"].ConnectionString; SqlConnection sqlconn = new SqlConnection(mainconn); string sqlquery = "SELECT UserID, Name, Mobile, Age, Date From tbluser"; SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn); sqlconn.Open(); SqlDataReader sdr = sqlcomm.ExecuteReader(CommandBehavior.CloseConnection); var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new PushStreamContent((stream, content, context) => { using (stream) using (sdr) { var serializer = new JsonSerializer(); using (var writer = new StreamWriter(stream)) using (var jsonWriter = new JsonTextWriter(writer)) { jsonWriter.WriteStartArray(); while (sdr.Read()) { var item = new TestClass() { UserId = sdr.GetString(0), Name = sdr.IsDBNull(1) ? string.Empty : sdr.GetString(1), Mobile = sdr.IsDBNull(2) ? string.Empty : sdr.GetString(2), Age = (sdr.GetValue(3) != DBNull.Value) ? Convert.ToInt32(sdr.GetValue(3)) : 0, Date = (sdr.GetValue(4) != DBNull.Value) ? Convert.ToDateTime(sdr.GetValue(4)) : (DateTime?)null }; serializer.Serialize(jsonWriter, item); jsonWriter.Flush(); } jsonWriter.WriteEndArray(); } } }, "application/json"); return response; }
3. 异步流式处理(优化性能)
结合异步读取数据,减少线程阻塞,同时保持流式输出的内存优势:
public async Task<IHttpActionResult> Get() { string mainconn = ConfigurationManager.ConnectionStrings["myconn"].ConnectionString; using (SqlConnection sqlconn = new SqlConnection(mainconn)) { await sqlconn.OpenAsync(); string sqlquery = "SELECT UserID, Name, Mobile, Age, Date From tbluser"; using (SqlCommand sqlcomm = new SqlCommand(sqlquery, sqlconn)) using (SqlDataReader sdr = await sqlcomm.ExecuteReaderAsync()) { var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new PushStreamContent(async (stream, content, context) => { using (stream) { var serializer = new JsonSerializer(); using (var writer = new StreamWriter(stream)) using (var jsonWriter = new JsonTextWriter(writer)) { jsonWriter.WriteStartArray(); while (await sdr.ReadAsync()) { var item = new TestClass() { UserId = sdr.GetString(0), Name = sdr.IsDBNull(1) ? string.Empty : sdr.GetString(1), Mobile = sdr.IsDBNull(2) ? string.Empty : sdr.GetString(2), Age = (sdr.GetValue(3) != DBNull.Value) ? Convert.ToInt32(sdr.GetValue(3)) : 0, Date = (sdr.GetValue(4) != DBNull.Value) ? Convert.ToDateTime(sdr.GetValue(4)) : (DateTime?)null }; serializer.Serialize(jsonWriter, item); jsonWriter.Flush(); } jsonWriter.WriteEndArray(); } } }, "application/json"); return ResponseMessage(response); } } }
三、关键注意点
- DataSet/DataTable不是大数据解决方案:它们无法解决内存溢出问题,只适合中小体量数据的结构化存储。
- 优先选择分页或流式响应:这两种方案从根本上减少了单次加载到内存的数据量,是处理大数据的核心思路。
- 必须使用
using语句:确保数据库连接、SqlDataReader等资源被正确释放,避免资源泄漏加剧内存压力。
内容的提问来源于stack exchange,提问作者user19613128
相关产品推荐
相关产品推荐

