ADO.NET
在 .NET 平台的后端开发中,数据库交互是核心能力之一,而 ADO.NET 作为微软官方提供的标准化数据访问技术栈,是操作关系型数据库的基石。本文将以 SQL Server 为例,系统梳理 ADO.NET 的五大核心对象、两种访问模式、事务处理与工具类封装,带你从零掌握连接式与断开式两套数据访问方案。
一、ADO.NET 概述
1.1 什么是 ADO.NET
ADO.NET 是 .NET Framework /.NET Core 中负责数据访问的类库集合,是应用程序与数据源之间的桥梁。它原生支持 SQL Server,同时可兼容 MySQL、Oracle 等多种关系型数据库,提供了完全面向对象的数据访问架构,可实现数据的查询、新增、修改、删除等全量操作。
1.2 核心优势
ADO.NET 最突出的设计是断开式数据访问架构:应用程序可以在与数据库断开连接的情况下,在内存中完成数据的读取、修改,最后再批量将变更同步回数据库。这种设计大幅减少了数据库连接的占用时长,降低了服务器资源消耗,尤其适合多层架构与高并发业务场景。
1.3 两大访问模式
- 连接式访问:全程保持数据库连接,通过
Command+DataReader逐行读取数据,适合大数据量、只读向前的高性能查询场景。 - 断开式访问:通过
DataAdapter将数据一次性填充到内存DataSet中,之后关闭连接,在内存中操作数据,最后统一回写数据库。
二、环境准备与命名空间
操作 SQL Server 无需额外下载第三方驱动,.NET 框架内置了对应类库,只需引入核心命名空间即可:
csharp
运行
using System.Data; // 数据通用基类(DataSet、DataTable 等) using System.Data.SqlClient; // SQL Server 专属数据提供程序补充说明:
System.Data.SqlClient是 .NET Framework 时代的标准库;若使用 .NET Core /.NET 5+ 平台,推荐使用Microsoft.Data.SqlClient,核心 API 完全向下兼容。
三、ADO.NET 五大核心对象
3.1 SqlConnection:数据库连接对象
SqlConnection负责建立应用程序与数据库之间的物理连接,是所有数据操作的入口。
(1)连接字符串
SQL Server 两种常用连接字符串格式:
- Windows 身份验证:
Data Source=服务器地址;Initial Catalog=数据库名;Integrated Security=True - SQL Server 身份验证:
Data Source=服务器地址;Initial Catalog=数据库名;User ID=用户名;Password=密码
示例:
csharp
运行
string connStr = "Data Source=.;Initial Catalog=TestDB;Integrated Security=True";(2)核心属性与方法
表格
| 属性 | 说明 |
|---|---|
| ConnectionString | 获取或设置数据库连接字符串 |
| State | 获取当前连接状态,ConnectionState枚举类型 |
表格
| 方法 | 说明 |
|---|---|
| Open() | 打开数据库连接 |
| Close() | 关闭数据库连接 |
ConnectionState常用枚举值:
Open:连接已打开Closed:连接已关闭Connecting:正在连接中Broken:连接中断,可关闭后重新打开
(3)标准连接示例
数据库连接属于稀缺资源,必须保证使用后释放,推荐配合try-catch-finally或using语句:
csharp
运行
using System;进行知识总结完成一篇 CSDN 博客创作
已完成思考
ADO.NET 完全指南:SQL Server 数据访问核心对象与实战封装(C#)
本文系统梳理 ADO.NET 访问 SQL Server 的完整知识体系,涵盖五大核心对象、连接式与断开式两种访问模式、事务处理及生产级 DBHelper 封装,附完整可运行代码,适合 .NET 开发者入门与复习查阅。
一、前言
在 .NET 生态中,ADO.NET 是微软官方提供的底层数据访问技术栈,是操作关系型数据库的基石。无论是 EF Core 等 ORM 框架,还是各类数据库操作组件,底层大多基于 ADO.NET 实现。掌握原生 ADO.NET,能帮助我们深刻理解数据库交互原理,排查底层性能与连接问题。
本文以SQL Server为数据源,从基础概念到核心对象,从事务处理到工具类封装,完整讲解 ADO.NET 的实战用法。
二、ADO.NET 核心概述
2.1 什么是 ADO.NET
ADO.NET 是 .NET Framework /.NET Core 中负责数据访问的类库集合,是应用程序与数据源之间的标准化桥梁。它原生深度支持 SQL Server,同时兼容 MySQL、Oracle 等主流关系型数据库,提供完全面向对象的数据访问架构,可实现数据的增、删、改、查全量操作。
其最核心的设计优势是断开式数据访问架构:应用程序可在与数据库断开连接的状态下,在内存中完成数据读取与修改,最后批量将变更同步回数据库,大幅减少数据库连接占用时长,降低服务器资源消耗。
2.2 两大数据访问模式
表格
| 访问模式 | 核心组件 | 特点 | 适用场景 |
|---|---|---|---|
| 连接式访问 | SqlCommand+SqlDataReader | 全程保持连接,只读向前逐行读取,内存占用低、读取速度快 | 大数据量只读查询、列表遍历 |
| 断开式访问 | SqlDataAdapter+DataSet/DataTable | 一次性加载数据到内存,断开连接后仍可操作,最后统一回写 | 离线数据编辑、小批量数据处理、多层架构数据传输 |
三、环境准备与命名空间
操作 SQL Server 无需额外下载第三方驱动,.NET 框架内置原生支持。
- .NET Framework:使用
System.Data.SqlClient命名空间 - .NET Core / .NET 5+:推荐使用 NuGet 包
Microsoft.Data.SqlClient,API 完全向下兼容
核心引入:
csharp
运行
using System.Data; // 通用数据类:DataSet、DataTable、枚举等 using System.Data.SqlClient; // SQL Server 原生数据提供程序四、五大核心对象详解与实战
4.1 SqlConnection:数据库连接对象
SqlConnection负责建立应用程序与 SQL Server 之间的物理连接,是所有数据操作的入口。
核心属性与方法
表格
| 属性 | 说明 |
|---|---|
ConnectionString | 数据库连接字符串 |
State | 当前连接状态,ConnectionState枚举类型 |
表格
| 方法 | 说明 |
|---|---|
Open() | 打开数据库连接 |
Close() | 关闭数据库连接 |
Dispose() | 释放连接资源 |
ConnectionState常用枚举:Open(已打开)、Closed(已关闭)、Connecting(连接中)、Broken(连接中断)。
连接字符串
SQL Server 两种常用认证方式:
csharp
运行
// Windows 身份验证(本地开发推荐) string connStr = "Data Source=.;Initial Catalog=TestDB;Integrated Security=True"; // SQL Server 身份验证 string connStr = "Data Source=127.0.0.1;Initial Catalog=TestDB;User ID=sa;Password=123456";标准连接示例
数据库连接是稀缺资源,必须保证使用后释放,推荐使用using语句自动释放:
csharp
运行
string connStr = "Data Source=.;Initial Catalog=TestDB;Integrated Security=True"; // using 语句执行结束后自动释放连接,无需手动 Close using (SqlConnection conn = new SqlConnection(connStr)) { try { conn.Open(); Console.WriteLine($"数据库连接成功,当前状态:{conn.State}"); } catch (Exception ex) { Console.WriteLine($"连接失败:{ex.Message}"); } }4.2 SqlCommand:SQL 命令执行对象
SqlCommand用于执行具体的 SQL 语句或存储过程,实现增删改查操作,执行过程中必须保持数据库连接。
核心属性与方法
表格
| 属性 | 说明 |
|---|---|
CommandText | 要执行的 SQL 语句 / 存储过程名 |
Connection | 关联的数据库连接对象 |
Parameters | SQL 参数集合(用于参数化查询) |
表格
| 方法 | 说明 | 适用场景 |
|---|---|---|
ExecuteNonQuery() | 返回受影响行数(int) | 增、删、改操作 |
ExecuteScalar() | 返回结果集首行首列(object) | 聚合查询(count、sum)、单值查询 |
ExecuteReader() | 返回SqlDataReader对象 | 多行多列查询 |
实战 1:执行增删改(ExecuteNonQuery)
csharp
运行
string connStr = "Data Source=.;Initial Catalog=TestDB;Integrated Security=True"; string sql = "INSERT INTO Users(UserName, Password) VALUES(@UserName, @Password)"; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand(sql, conn)) { // 参数化查询,防止 SQL 注入(开发必须规范) cmd.Parameters.AddWithValue("@UserName", "zhangsan"); cmd.Parameters.AddWithValue("@Password", "123456"); int rows = cmd.ExecuteNonQuery(); Console.WriteLine(rows > 0 ? "新增成功" : "新增失败"); } }✅ 重要规范:禁止字符串拼接 SQL,必须使用参数化查询,杜绝 SQL 注入风险。
实战 2:单值查询(ExecuteScalar)
csharp
运行
string sql = "SELECT COUNT(*) FROM Users"; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); SqlCommand cmd = new SqlCommand(sql, conn); int count = Convert.ToInt32(cmd.ExecuteScalar()); Console.WriteLine($"用户总数:{count}"); }4.3 SqlDataReader:只读向前数据读取器
SqlDataReader是连接式查询的核心,以只读、向前的方式逐行读取数据,内存占用极低,读取性能优异。
核心属性与方法
表格
| 属性 | 说明 |
|---|---|
HasRows | 是否包含数据(bool) |
FieldCount | 当前行的列数 |
表格
| 方法 | 说明 |
|---|---|
Read() | 前进到下一行,有数据返回 true,无数据返回 false |
Close() | 关闭读取器 |
GetInt32() / GetString() | 按列索引获取指定类型的值 |
完整查询示例
csharp
运行
string sql = "SELECT Id, UserName, Password FROM Users"; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); SqlCommand cmd = new SqlCommand(sql, conn); using (SqlDataReader dr = cmd.ExecuteReader()) { Console.WriteLine("编号\t用户名\t密码"); while (dr.Read()) // 逐行读取 { // 两种取值方式:列名索引 / 列序号索引 int id = dr.GetInt32(0); string userName = dr["UserName"].ToString(); string password = dr["Password"].ToString(); Console.WriteLine($"{id}\t{userName}\t{password}"); } } }⚠️ 注意:
SqlDataReader读取期间必须保持数据库连接打开;使用完毕必须及时关闭,否则会占用连接资源。
4.4 DataSet & DataTable:内存数据集合
DataSet可以理解为内存中的数据库,是一个独立于数据源的数据容器,断开数据库连接后依然可用。内部以 XML 格式存储数据,可包含多张DataTable,并支持表关系、数据约束、行状态管理。
DataSet:数据表的集合,包含多个DataTableDataTable:内存中的一张数据表,由DataRow(行)和DataColumn(列)组成
内存构建 DataTable 示例
csharp
运行
// 创建数据集 DataSet ds = new DataSet("MyDataSet"); // 创建数据表并加入数据集 DataTable dt = new DataTable("UserInfo"); ds.Tables.Add(dt); // 添加列结构 dt.Columns.Add("Id", typeof(int)); dt.Columns.Add("Name", typeof(string)); dt.Columns.Add("Age", typeof(int)); // 添加数据行 dt.Rows.Add(1, "张三", 25); dt.Rows.Add(2, "李四", 30); // 条件过滤 DataRow[] rows = dt.Select("Age > 25"); // 修改数据 dt.Rows[0]["Name"] = "张三_更新"; // 删除行 dt.Rows[1].Delete(); // 遍历数据 foreach (DataRow row in dt.Rows) { Console.WriteLine($"{row["Id"]}\t{row["Name"]}\t{row["Age"]}"); }4.5 SqlDataAdapter:数据适配器(断开式核心)
SqlDataAdapter是连接数据库与DataSet的桥梁,负责从数据库拉取数据填充到内存DataSet,也负责将内存中的变更批量回写到数据库。
核心方法
Fill(DataSet ds):执行查询,将数据填充到 DataSetUpdate(DataSet ds):将 DataSet 中的变更批量更新回数据库
填充 DataTable 示例
csharp
运行
string connStr = "Data Source=.;Initial Catalog=TestDB;Integrated Security=True"; string sql = "SELECT Id, UserName, Password FROM Users"; // DataAdapter 会自动管理连接,无需手动 Open/Close SqlDataAdapter sda = new SqlDataAdapter(sql, connStr); DataTable dt = new DataTable(); sda.Fill(dt); // 自动打开连接、填充数据、关闭连接 // 离线状态下直接遍历数据 foreach (DataRow row in dt.Rows) { Console.WriteLine($"{row["Id"]}\t{row["UserName"]}\t{row["Password"]}"); }五、SQL Server 事务处理
事务用于保证一组 SQL 操作要么全部成功、要么全部失败,典型场景如转账、订单生成。ADO.NET 通过SqlTransaction实现事务控制。
事务核心步骤
- 打开连接后开启事务:
conn.BeginTransaction() - 将事务对象绑定到 SqlCommand:
cmd.Transaction = transaction - 全部执行成功:
transaction.Commit()提交 - 出现异常:
transaction.Rollback()回滚
完整转账示例
csharp
运行
string connStr = "Data Source=.;Initial Catalog=TestDB;Integrated Security=True"; using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); // 开启事务,可指定隔离级别 SqlTransaction tran = conn.BeginTransaction(IsolationLevel.Serializable); try { // 操作1:张三账户扣减500 SqlCommand cmd1 = new SqlCommand( "UPDATE Account SET Balance = Balance - 500 WHERE Name='张三'", conn, tran); cmd1.ExecuteNonQuery(); // 操作2:李四账户增加500 SqlCommand cmd2 = new SqlCommand( "UPDATE Account SET Balance = Balance + 500 WHERE Name='李四'", conn, tran); cmd2.ExecuteNonQuery(); // 全部成功,提交事务 tran.Commit(); Console.WriteLine("转账成功"); } catch (Exception ex) { // 出现异常,回滚所有操作 tran.Rollback(); Console.WriteLine($"转账失败,已回滚:{ex.Message}"); } }六、通用 DBHelper 工具类封装(生产级)
原生 ADO.NET 代码重复度高,实际开发中通常封装为DBHelper工具类,统一管理连接、异常处理与资源释放。
封装后的 DBHelper 类
csharp
运行
/// <summary> /// SQL Server 数据库操作帮助类 /// </summary> public class DBHelper { private readonly string _connectionString; /// <summary> /// 构造函数传入连接字符串 /// </summary> public DBHelper(string connectionString) { _connectionString = connectionString; } /// <summary> /// 执行增删改,返回受影响行数 /// </summary> public int ExecuteNonQuery(string sql, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(_connectionString)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null && parameters.Length > 0) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteNonQuery(); } } /// <summary> /// 执行查询,返回首行首列 /// </summary> public object ExecuteScalar(string sql, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(_connectionString)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null && parameters.Length > 0) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteScalar(); } } /// <summary> /// 执行查询,返回 DataTable(离线) /// </summary> public DataTable ExecuteQuery(string sql, params SqlParameter[] parameters) { using (SqlConnection conn = new SqlConnection(_connectionString)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { if (parameters != null && parameters.Length > 0) cmd.Parameters.AddRange(parameters); SqlDataAdapter sda = new SqlDataAdapter(cmd); DataTable dt = new DataTable(); sda.Fill(dt); return dt; } } }调用示例
csharp
运行
// 初始化帮助类 DBHelper db = new DBHelper("Data Source=.;Initial Catalog=TestDB;Integrated Security=True"); // 登录验证(参数化查询) string sql = "SELECT COUNT(*) FROM Users WHERE UserName=@UserName AND Password=@Password"; SqlParameter[] paras = { new SqlParameter("@UserName", "zhangsan"), new SqlParameter("@Password", "123456") }; int count = Convert.ToInt32(db.ExecuteScalar(sql, paras)); Console.WriteLine(count > 0 ? "登录成功" : "用户名或密码错误");七、两种访问模式对比与选型
表格
| 维度 | 连接式(DataReader) | 断开式(DataSet/DataTable) |
|---|---|---|
| 连接占用 | 全程保持连接 | 填充后即释放连接 |
| 内存占用 | 低,逐行读取 | 高,全量加载到内存 |
| 读取速度 | 快 | 相对较慢 |
| 数据编辑 | 只读,不可修改 | 支持离线增删改 |
| 适用场景 | 大数据量列表查询、只读遍历 | 小批量数据编辑、多层架构数据传递、数据绑定 |
八、最佳实践与常见避坑
- 必须使用参数化查询:禁止字符串拼接 SQL,防止 SQL 注入
- 及时释放连接:优先使用
using语句自动释放连接、命令、读取器资源 - 避免长时间占用连接:数据读取完成后立即关闭连接,不要持有长连接
- 异常处理:数据库操作必须包裹异常捕获,保证异常时连接能正常关闭
- 大数据量查询:优先使用
SqlDataReader而非DataTable,避免内存溢出 - 事务范围:事务内只包含必要的数据库操作,避免长事务引发锁问题
九、总结
ADO.NET 是 .NET 数据库开发的底层根基:SqlConnection负责建立连接,SqlCommand负责执行命令,SqlDataReader实现连接式高速读取,DataSet+SqlDataAdapter构建断开式离线数据模型,SqlTransaction保证数据一致性。
掌握原生 ADO.NET 后,再学习 EF Core、Dapper 等 ORM 框架会更加轻松,也能更精准地定位和解决数据访问层的性能问题。