当前位置: 首页 > news >正文

ASP.NET 实战:用 DataReader 秒级读取用户数据并导出 CSV

在这里插入图片描述

摘要

本文以一个实际可运行的 ASP.NET WebForms 示例(项目名:WebSite6-4,页面:DataReader.aspx)为载体,讲解如何使用 SqlDataReader(即题目中的 DataReader 对象)从数据库读取用户数据并展示到页面中。文章风格贴近日常交流,侧重实战:包括完整代码、逐段详细解析、示例测试数据与输出、以及复杂度分析和总结。你可以把本文当成把散乱代码整理成可用功能的手册。

描述(场景)

想象你在做一个简单的博客后台或管理系统,数据库里有一张 Users 表,存放网站注册用户的数据。管理员需要一个页面:

  • 初次打开时展示用户列表;
  • 支持按用户名模糊搜索或按角色过滤(例如:管理员、编辑、普通用户);
  • 支持导出当前查询结果为 CSV(便于备份或在 Excel 中查看);
  • 要求读取过程尽量快、占用资源少(表可能有成千上万条记录)。

这正是 SqlDataReader 发挥优势的场景:它是快速、只向前、基于连接的读取器,适合逐行、流式读取大结果集,内存占用低。

下面我们把题目中给的零散代码整理成一个完整、合理、且具备实际功能的页面:

  • 页面名:DataReader.aspx(和 DataReader.aspx.cs 后端);
  • 功能:显示用户表、支持搜索/过滤、导出 CSV;
  • 注意事项:使用参数化查询防注入、使用 using 释放资源、对空值处理与 HTML 编码以防 XSS。

题解答案(完整代码)

下面给出完整的页面和后端代码,拷贝到 WebSite6-4 项目对应文件即可运行(注意替换连接字符串)。

DataReader.aspx(页面标记)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="DataReader.aspx.cs" Inherits="WebSite6_4_DataReader" %>
<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>用户列表(DataReader 示例)</title>
</head>
<body><form id="form1" runat="server"><div><label>用户名:</label><asp:TextBox ID="txtSearch" runat="server" Width="200"></asp:TextBox><label>角色:</label><asp:DropDownList ID="ddlRole" runat="server"><asp:ListItem Value="">全部</asp:ListItem><asp:ListItem Value="Admin">管理员</asp:ListItem><asp:ListItem Value="Editor">编辑</asp:ListItem><asp:ListItem Value="User">普通用户</asp:ListItem></asp:DropDownList><asp:Button ID="btnSearch" runat="server" Text="查询" OnClick="btnSearch_Click" /><asp:Button ID="btnExport" runat="server" Text="导出 CSV" OnClick="btnExport_Click" /></div><hr /><asp:Literal ID="ltResult" runat="server"></asp:Literal></form>
</body>
</html>

DataReader.aspx.cs(后端 C#)

using System;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Web;
using System.Web.UI;public partial class WebSite6_4_DataReader : System.Web.UI.Page
{// 请根据你的环境修改连接字符串(示例使用 Windows 身份验证连接本机)private static readonly string ConStr = "Server=.;Database=MyBlog;Integrated Security=true;";protected void Page_Load(object sender, EventArgs e){if (!IsPostBack){// 初次加载显示全部用户(或可限制条数以防表过大)LoadUsers(null, null);}}protected void btnSearch_Click(object sender, EventArgs e){string username = txtSearch.Text.Trim();string role = ddlRole.SelectedValue;LoadUsers(username, role);}private void LoadUsers(string usernameFilter, string roleFilter){StringBuilder sb = new StringBuilder();using (SqlConnection conn = new SqlConnection(ConStr))using (SqlCommand cmd = conn.CreateCommand()){// 基本查询,选出常用字段cmd.CommandText = @"SELECT Id, Username, FullName, Email, Role, CreatedAt FROM Users WHERE 1=1";if (!string.IsNullOrEmpty(usernameFilter)){cmd.CommandText += " AND Username LIKE @username";cmd.Parameters.AddWithValue("@username", "%" + usernameFilter + "%");}if (!string.IsNullOrEmpty(roleFilter)){cmd.CommandText += " AND Role = @role";cmd.Parameters.AddWithValue("@role", roleFilter);}cmd.CommandText += " ORDER BY CreatedAt DESC"; // 按注册时间倒序显示,常见需求conn.Open();// 使用 CommandBehavior.CloseConnection 可以在 reader 关闭时自动关闭连接using (SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)){// 如果没有行,给出友好提示if (!dr.HasRows){ltResult.Text = "<p>没有找到符合条件的用户。</p>";return;}// 构建表格头部(字段名)sb.AppendLine("<table border=1 cellpadding=6 cellspacing=0>");sb.AppendLine("<tr style=\"background:#f0f0f0;\">");for (int i = 0; i < dr.FieldCount; i++){// HtmlEncode 输出,防止存库后有异常字符或 XSSsb.AppendFormat("<th>{0}</th>", HttpUtility.HtmlEncode(dr.GetName(i)));}sb.AppendLine("</tr>");// 逐行读取while (dr.Read()){sb.AppendLine("<tr>");for (int j = 0; j < dr.FieldCount; j++){object val = dr.GetValue(j);// 对空值做处理string text = val == DBNull.Value ? "" : val.ToString();// 对日期做格式化(示例)if (val is DateTime dt){text = dt.ToString("yyyy-MM-dd HH:mm:ss");}sb.AppendFormat("<td>{0}</td>", HttpUtility.HtmlEncode(text));}sb.AppendLine("</tr>");}sb.AppendLine("</table>");ltResult.Text = sb.ToString();}}}// 导出 CSV(使用相同的查询条件)protected void btnExport_Click(object sender, EventArgs e){string username = txtSearch.Text.Trim();string role = ddlRole.SelectedValue;using (SqlConnection conn = new SqlConnection(ConStr))using (SqlCommand cmd = conn.CreateCommand()){cmd.CommandText = @"SELECT Id, Username, FullName, Email, Role, CreatedAt FROM Users WHERE 1=1";if (!string.IsNullOrEmpty(username)){cmd.CommandText += " AND Username LIKE @username";cmd.Parameters.AddWithValue("@username", "%" + username + "%");}if (!string.IsNullOrEmpty(role)){cmd.CommandText += " AND Role = @role";cmd.Parameters.AddWithValue("@role", role);}cmd.CommandText += " ORDER BY CreatedAt DESC";conn.Open();using (SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)){// 输出 CSV 到响应流Response.Clear();Response.ContentType = "text/csv";Response.ContentEncoding = System.Text.Encoding.UTF8;Response.Charset = "utf-8";Response.AddHeader("Content-Disposition", "attachment;filename=Users.csv");// 写表头for (int i = 0; i < dr.FieldCount; i++){if (i > 0) Response.Write(",");Response.Write('"' + dr.GetName(i).Replace("\"", "\"\"") + '"');}Response.Write("\n");// 写数据行while (dr.Read()){for (int j = 0; j < dr.FieldCount; j++){if (j > 0) Response.Write(",");object val = dr.GetValue(j);string text = val == DBNull.Value ? "" : val.ToString();if (val is DateTime dt){text = dt.ToString("yyyy-MM-dd HH:mm:ss");}// 简单 CSV 转义(把双引号替换为两个双引号)text = text.Replace("\"", "\"\"");Response.Write('"' + text + '"');}Response.Write("\n");}Response.Flush();Response.End();}}}
}

题解代码分析(逐段说明)

下面把关键代码段拆开来解释,顺序对应上面的实现:

连接字符串与基本结构

private static readonly string ConStr = "Server=.;Database=MyBlog;Integrated Security=true;";

说明:这条用于连接本地 SQL Server 的字符串采用 Windows 身份验证(Integrated Security=true)。如果你使用 SQL 登录,请替换为 Server=服务器名;Database=数据库名;User Id=用户名;Password=密码;。把连接信息放到 web.config 中更安全(避免源码硬编码)。

LoadUsers 方法(主读取逻辑)

using (SqlConnection conn = new SqlConnection(ConStr))
using (SqlCommand cmd = conn.CreateCommand())
{cmd.CommandText = "SELECT ... FROM Users WHERE 1=1";// 根据条件追加 SQL,并添加参数conn.Open();using (SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection)){while (dr.Read()) { ... }}
}

要点:

  • using 块保证无论是否抛异常都会正确释放资源(IDisposable);
  • 拼接 SQL 时使用参数 @username@role 并通过 Parameters.AddWithValue 传值,防止 SQL 注入;
  • ExecuteReader(CommandBehavior.CloseConnection):当 dr 关闭后,连接也会自动关闭,这是一个常见且安全的做法;
  • dr.HasRows 可以先判断是否有结果,给出友好提示;
  • dr.FieldCountdr.GetName(i)dr.GetValue(i) 用于动态列处理(不需要硬编码列头);
  • 输出到页面时用 HttpUtility.HtmlEncode(...) 避免用户数据里含有 HTML 或脚本导致的 XSS 漏洞。

处理 DBNull、类型与格式化

object val = dr.GetValue(j);
string text = val == DBNull.Value ? "" : val.ToString();
if (val is DateTime dt) { text = dt.ToString("yyyy-MM-dd HH:mm:ss"); }
sb.AppendFormat("<td>{0}</td>", HttpUtility.HtmlEncode(text));

解释:读取数据库值时要考虑空值(DBNull.Value),另外对于 DateTime 等类型可以做格式化,以便展示更友好的人类可读字符串。

导出 CSV(流式输出)

导出 CSV 的实现沿用了相同的 SQL 查询,但输出到 Response,同时注意以下几点:

  • 设置 Response.ContentType = "text/csv" 并设置 Content-Disposition 以便浏览器弹出下载对话框;
  • 写表头、写数据行、对包含双引号的字段做转义(CSV 中双引号用两个双引号表示);
  • 对于非常大的表,可能需要禁用 Response 缓冲(例如 Response.BufferOutput = false)并周期性 Response.Flush(),以避免内存占用过高;
  • Response.End() 会抛出 ThreadAbortException,如果不喜欢这个行为,可以改用 HttpContext.Current.ApplicationInstance.CompleteRequest()

示例测试及结果

下面给出一个简化的 Users 表结构和测试数据,配合页面可以直接观察输出结果:

CREATE TABLE Users(Id INT IDENTITY(1,1) PRIMARY KEY,Username NVARCHAR(50),FullName NVARCHAR(100),Email NVARCHAR(100),Role NVARCHAR(50),CreatedAt DATETIME
);INSERT INTO Users (Username, FullName, Email, Role, CreatedAt) VALUES
('zhangsan', '张三', 'zhangsan@example.com', 'User', '2024-01-10 09:00:00'),
('lisi', '李四', 'lisi@example.com', 'Editor', '2024-03-02 14:22:10'),
('admin', '系统管理员', 'admin@example.com', 'Admin', '2023-12-30 08:05:05');

在页面上:

  • 初次打开会看到一个三列(Id/Username/FullName/Email/Role/CreatedAt)的表格,按 CreatedAt 倒序;
  • 输入 zhang 做模糊搜索,能返回 zhangsan 的行;
  • 选择角色 Admin 并查询,只会显示 admin
  • 点击导出 CSV,会下载名为 Users.csv 的文件,内容格式类似:
"Id","Username","FullName","Email","Role","CreatedAt"
"3","admin","系统管理员","admin@example.com","Admin","2023-12-30 08:05:05"
...

上面这些行为可以在本地站点运行后直接验证。

时间复杂度

读取 n 行记录时,主要耗时是遍历结果集并把每一行写到 HTML(或 CSV)中,因此时间复杂度为 O(n * m),其中 n 为行数,m 为每行列数(通常认为常量),常简化为 O(n)。也就是说,随着读取行数线性增长,耗时大体上也线性增长。

需要注意:如果查询本身有复杂的 WHERE、JOIN 或未命中索引,那数据库端的查询成本会更高 —— 这不是 DataReader 的问题,是 SQL 查询与索引策略的问题。

空间复杂度

使用 SqlDataReader 的优点之一是低内存占用:它是基于连接的流式读取器,数据库返回多少,代码就读取多少,通常只在内存中保留当前一行的数据。因此空间复杂度大致为 O(1)(忽略用于输出 HTML 的缓冲 StringBuilder,如果你把整个结果放进 StringBuilder,内存会随行数增加;为减少内存,建议直接把行写入响应流或分页读取)。

如果输出较大,建议采用分页查询(TOP / OFFSET-FETCHROW_NUMBER())或直接把读取结果逐行 Response.Write 并周期 Response.Flush() 来避免占用过多内存。

总结

  • SqlDataReader 非常适合需要快速、逐行读取大量数据的场景(例如导出、生成流式响应、展示长列表);
  • 使用 using、参数化查询和 HttpUtility.HtmlEncode 是三条重要的实践:保证资源释放、防止注入与防止 XSS;
  • 对于需要回溯、修改或绑定到 UI 的场景,可以考虑 DataTable/DataSet(断开连接)或 ORM(比如 EF);如果只是只读展示或导出,DataReader 更省内存且更快;
  • 大数据量时,尽量在 SQL 端做筛选与分页,避免将完整表一次性读到内存;
  • 导出文件时注意字符编码(如 UTF-8 BOM 的需求)和 CSV 转义规则。

文章转载自:

http://R7FVjH2r.qbjrL.cn
http://x2L7Phl9.qbjrL.cn
http://MZT8zWdu.qbjrL.cn
http://zvw2fPQu.qbjrL.cn
http://AQoxxKda.qbjrL.cn
http://dFFxGdO6.qbjrL.cn
http://RYPAEjDp.qbjrL.cn
http://YwOG1VOh.qbjrL.cn
http://2oiaFr03.qbjrL.cn
http://olT1hcT5.qbjrL.cn
http://i0Mv3e6r.qbjrL.cn
http://L7i6b5FZ.qbjrL.cn
http://T8MI7uxK.qbjrL.cn
http://KGK7RLFn.qbjrL.cn
http://W3H2RY4a.qbjrL.cn
http://gkMmUB4I.qbjrL.cn
http://od0evNZw.qbjrL.cn
http://u7unXWHU.qbjrL.cn
http://cKKgBirT.qbjrL.cn
http://5daXwTsR.qbjrL.cn
http://nKatHH1L.qbjrL.cn
http://vZwhSszI.qbjrL.cn
http://p77Dledy.qbjrL.cn
http://YiBy4MJ4.qbjrL.cn
http://V9R9jDUQ.qbjrL.cn
http://hUHe4Qic.qbjrL.cn
http://Gy91eVPV.qbjrL.cn
http://9lrmNoZF.qbjrL.cn
http://Ho7vebLL.qbjrL.cn
http://r1VlhOEc.qbjrL.cn
http://www.dtcms.com/a/386033.html

相关文章:

  • 如何使用 Python 程序把 PDF 文件转换成长图 PNG 格式输出图片?
  • 从Dubbo到SpringCloud Alibaba:大型项目迁移的实战手册(含成本分析与踩坑全记录)(二)
  • vue3 + ts + uniappX 封装上传文件(image pdf)、预览文件功能
  • PDF/图像/音视频一体化处理方案
  • 【数据结构】 深入理解 LinkedList 与链表
  • Hadoop HDFS-高可用集群部署
  • 深入汇编底层与操作系统系统调用接口:彻底掰开揉碎c语言简单的一行代码-打印helloworld是如何从C语言点击运行到显示在屏幕上的
  • ARM3.(汇编函数和c语言相互调用及ARM裸机开发环境搭建)
  • LeetCode 380 - O(1) 时间插入、删除和获取随机元素
  • 9 基于机器学习进行遥感影像参数反演-以随机森林为例
  • DB Hitek宣布推出650V GaN HEMT工艺
  • 机器学习简单数据分析案例
  • [特殊字符] 欢迎使用 C++ Arrow 函数 - 革命性的新特性!
  • 外网访问分布式跟踪系统 zipkin
  • Base 发币在即:L2 代币能否撬动生态增长?
  • DRDR生态Token正式上线BitMart,开启全球化新篇章
  • Spring Boot 3 + EasyExcel 文件导入导出实现
  • 9.16总结
  • Android开机时间查看
  • 探针水平的表达矩阵转换为基因水平的表达矩阵是芯片数据分析中关键的一步
  • PHP基础-语法初步(第七天)
  • 奥威BI与ChatBI:自然语言交互赋能企业数据分析新体验
  • Vue: 组件基础
  • 亚马逊云科技 EC2 服务终端节点:安全高效访问云服务的利器
  • 2026届计算机毕业设计选题 大数据毕业设计选题推荐 题目新颖 数据分析 可视化大屏 通过率高
  • html实现文字横向对齐以及margin的解释
  • 如何轻松找到并畅玩Edge浏览器隐藏的冲浪小游戏
  • K8S中的神秘任务Job与CronJob
  • go grpc开发使用
  • [论文阅读] 人工智能 + 软件工程 | 告别冗余HTML与高算力消耗:EfficientUICoder如何破解UI2Code的token难题