You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Web API读取大量数据内存溢出问题及DataSet等方案咨询

问题描述

SQL Server数据库存储了大量数据,已开发Web API读取记录。使用SqlDataReader读取全部数据时,在Google Chrome测试出现内存溢出问题。希望使用DataSet处理这些数据,请问:

  1. 如何将DataSet与SqlDataReader结合使用?
  2. 是否需要改用DataTable配合DataSet?
  3. 还有哪些处理大量数据的可行方案?

现有代码如下:

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);
        }
    }
}

三、关键注意点

  1. DataSet/DataTable不是大数据解决方案:它们无法解决内存溢出问题,只适合中小体量数据的结构化存储。
  2. 优先选择分页或流式响应:这两种方案从根本上减少了单次加载到内存的数据量,是处理大数据的核心思路。
  3. 必须使用using语句:确保数据库连接、SqlDataReader等资源被正确释放,避免资源泄漏加剧内存压力。

内容的提问来源于stack exchange,提问作者user19613128

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 19:39:45