SqlConnection建立连接,用
ExecuteReader()获取
SqlDataReader对象,再调用其
Read()方法遍历数据库结果集
C#中遍历数据库是一个常见的需求,通常涉及建立连接、执行SQL命令、读取结果集等步骤,以下是详细的实现方法和最佳实践:
基础流程
使用ADO.NET框架提供的类库(如SqlConnection, SqlCommand, SqlDataReader)是最核心的方式,基本步骤包括:创建连接对象→配置连接字符串→打开连接→构建并执行SQL命令→通过数据读取器逐行检索数据→关闭资源释放内存,这种方式效率高且适用于大多数场景,尤其是处理大量数据时。
具体实现方法对比表
| 技术手段 | 适用场景 | 优点 | 注意事项 |
|---|---|---|---|
SqlDataReader |
单向只读顺序访问(推荐大宗数据) | 内存占用低,性能优异 | 必须保持连接开启直到用完 |
DataSet/DataTable |
离线操作或多层架构传递 | 支持随机访问、缓存修改 | 序列化成本较高 |
Entity Framework |
ORM映射复杂对象关系 | 自动化程度高,减少手写SQL | 学习曲线较陡 |
LINQ to SQL |
强类型查询与编译时检查 | 语法简洁近似于对象集合操作 | 底层仍依赖传统ADO.NET机制 |
核心代码示例详解
使用SqlDataReader逐行读取(最常用方案)
using System.Data;
using System.Data.SqlClient;
string connectionString = "Data Source=服务器地址;Initial Catalog=数据库名;Integrated Security=True";
string query = "SELECT FROM TableName WHERE Conditions"; // 根据实际需求编写SQL语句
// 初始化组件
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open(); // 显式打开连接
using (SqlCommand cmd = new SqlCommand(query, conn))
{
using (SqlDataReader reader = cmd.ExecuteReader()) // 自动关联当前活动的connection
{
while (reader.Read()) // HasRows属性控制循环终止条件
{
int id = reader.GetInt32(0); // 按索引取值(从0开始)
string name = reader["NameColumn"].ToString(); // 也可通过列名获取
Console.WriteLine($"ID: {id}, Name: {name}");
}
} // reader.Close()和Dispose()在此自动调用
}
} // conn.Close()在此自动调用
️ 关键点:务必用using语句确保非托管资源正确释放;若遇到异常应捕获并回滚事务(未展示但重要)。
填充到DataSet进行缓存式访问
当需要多次反复访问同一数据集时,可先将结果加载到内存:
DataSet ds = new DataSet();
using (SqlConnection conn = new SqlConnection(connStr))
{
SqlDataAdapter adapter = new SqlDataAdapter("SELECT FROM Orders", conn);
adapter.Fill(ds, "OrdersTable"); // 第二个参数指定表别名便于后续引用
}
// 现在可以脱机操作这些数据
foreach (DataTable table in ds.Tables)
{
foreach (DataRow row in table.Rows)
{
foreach (var item in row.ItemArray) // 遍历当前行的所有单元格值
{
Console.Write($"{item}t");
}
Console.WriteLine();
}
}
此模式适合中小型数据集,但注意大数据量可能导致内存溢出。
高级技巧与优化建议
- 参数化查询防注入攻击
永远不要直接拼接用户输入到SQL语句中!改用带参数占位符的形式:SqlCommand cmd = new SqlCommand("SELECT FROM Users WHERE UserID @UserID", conn); cmd.Parameters.AddWithValue("@UserID", userInput); // 系统会自动处理特殊字符转义 - 分页加载提升响应速度
对于百万级记录表,采用TOP子句配合偏移量实现物理分页比应用程序层过滤更高效:SELECT FROM (SELECT ROW_NUMBER() OVER (ORDER BY CreateTime DESC) AS RowNum, FROM LogEntries) AS t WHERE RowNum > 100 AND RowNum <= 200;
- 异步编程避免UI阻塞
在WPF/WinForm应用中使用异步版本的方法防止界面冻结:public async Task<List<MyModel>> GetAsyncData() { return await Task.Run(() => { / 同步逻辑包装成任务 / }); } - 连接池复用机制利用
默认情况下ADO.NET会维护一个连接池,频繁开关同一个连接的实际开销远小于新建连接的成本,因此尽量重用而非频繁创建销毁连接对象。
常见错误排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| “无效的列名‘XXX’” | SQL返回字段与代码引用不匹配 | 检查数据库视图是否更新,或改用列序号代替名称 |
| “超时时间已过期” | 网络延迟过高/查询复杂度超标 | 增加CommandTimeout属性值,优化索引设计 |
| “并发修改冲突” | 多线程同时读写同一数据集 | 启用乐观锁机制,或改用悲观并发控制策略 |
| “未能找到路径……” | 相对路径解析错误 | 确保binDebug目录下存在必要的配置文件副本 |
FAQs相关问答
Q1: 如果我只想获取第一行的第一列该怎么写?
A: 可以在reader.Read()之后立即调用object result = reader.GetValue(0);,或者更明确地指定类型如int value = reader.GetInt32(0);,注意此时仍需完成一次完整的Read()调用才能定位到该位置。
Q2: 为什么有时候关闭了DataReader却仍然无法释放数据库连接?
A: 因为可能存在未处理完的结果集(例如存储过程返回了多个结果标签),这时需要在创建SqlCommand时设置MultipleActiveResultSets=true参数,并在消费完所有结果后再关闭读者对象,`cmd.CommandText = “EXEC MyProcedure”; cmd.CommandType = CommandType.StoredProcedure; cmd.
