-
ADO.NET基础:连接数据库与执行SQL
ADO.NET基础:连接数据库与执行SQL
概述
ADO.NET是C#操作关系型数据库的核心技术(如SQL Server、MySQL、Oracle),它提供了一套组件来实现连接数据库、执行SQL语句、处理结果集三大功能。核心组件包括:
Connection:建立与数据库的连接(如SqlConnection对应SQL Server);
Command:执行SQL语句或存储过程(如SqlCommand);
DataReader:只读向前的数据流(内存占用小,适合大数据量);
DataAdapter:填充DataSet/DataTable(断开式连接,适合离线处理数据);
DataSet:内存中的数据库(包含多个表、关系,断开连接后仍可操作)。
本节以SQL Server为例,通过完整实例讲解ADO.NET的基础用法,重点强调资源释放和防止SQL注入。
-
实例代码:完整的数据库操作流程
以下实例实现连接SQL Server、查询用户、插入用户、更新用户、删除用户,并使用参数化查询避免SQL注入:
csharp
using System;
using System.Data;
using System.Data.SqlClient; // SQL Server专用组件(MySQL需用MySql.Data.MySqlClient)
class AdoNetExample
{
// 连接字符串:SQL Server认证(替换为你的服务器、数据库、账号密码)
private const string ConnectionString =
"Server=localhost;Database=TestDB;User Id=sa;Password=YourPassword;TrustServerCertificate=True;";
static void Main()
{
try
{
// -------------------------- 1. 查询用户(DataReader) --------------------------
Console.WriteLine("=== 查询所有用户 ===");
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open(); // 打开连接
string sql = "SELECT Id, Name, Age FROM Users";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
using (SqlDataReader reader = cmd.ExecuteReader()) // 执行查询,返回DataReader
{
while (reader.Read()) // 逐行读取数据
{
Console.WriteLine($"ID: {reader["Id"]}, Name: {reader["Name"]}, Age: {reader["Age"]}");
}
}
}
} // using自动释放连接
// -------------------------- 2. 插入用户(参数化) --------------------------
Console.WriteLine("
=== 插入用户 ===");
int newUserId = InsertUser("张三", 25);
Console.WriteLine($"插入成功,新用户ID:{newUserId}");
// -------------------------- 3. 更新用户(参数化) --------------------------
Console.WriteLine("
=== 更新用户 ===");
bool updateSuccess = UpdateUser(newUserId, "李四");
Console.WriteLine(updateSuccess ? "更新成功" : "更新失败");
// -------------------------- 4. 删除用户(参数化) --------------------------
Console.WriteLine("
=== 删除用户 ===");
bool deleteSuccess = DeleteUser(newUserId);
Console.WriteLine(deleteSuccess ? "删除成功" : "删除失败");
// -------------------------- 5. 用DataAdapter填充DataSet --------------------------
Console.WriteLine("
=== 用DataSet查询用户 ===");
DataSet ds = GetUsersDataSet();
DataTable userTable = ds.Tables["Users"];
foreach (DataRow row in userTable.Rows)
{
Console.WriteLine($"ID: {row["Id"]}, Name: {row["Name"]}, Age: {row["Age"]}");
}
}
catch (SqlException ex)
{
Console.WriteLine($"数据库错误:{ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"系统错误:{ex.Message}");
}
}
// 插入用户(返回自增ID)
private static int InsertUser(string name, int age)
{
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
// 参数化SQL:@Name、@Age是参数占位符
string sql = "INSERT INTO Users (Name, Age) OUTPUT INSERTED.Id VALUES (@Name, @Age)";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
// 添加参数(防止SQL注入)
cmd.Parameters.AddWithValue("@Name", name);
cmd.Parameters.AddWithValue("@Age", age);
// 执行并返回自增ID
return (int)cmd.ExecuteScalar();
}
}
}
// 更新用户
private static bool UpdateUser(int id, string newName)
{
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
string sql = "UPDATE Users SET Name = @NewName WHERE Id = @Id";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@NewName", newName);
cmd.Parameters.AddWithValue("@Id", id);
// ExecuteNonQuery返回受影响的行数
return cmd.ExecuteNonQuery() > 0;
}
}
}
// 删除用户
private static bool DeleteUser(int id)
{
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
string sql = "DELETE FROM Users WHERE Id = @Id";
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@Id", id);
return cmd.ExecuteNonQuery() > 0;
}
}
}
// 用DataAdapter填充DataSet
private static DataSet GetUsersDataSet()
{
DataSet ds = new DataSet();
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
string sql = "SELECT Id, Name, Age FROM Users";
// DataAdapter自动处理连接的打开/关闭
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn))
{
adapter.Fill(ds, "Users"); // 填充DataSet,表名"Users"
}
}
return ds;
}
}
-
逐行讲解
2.1 连接字符串
ConnectionString:由服务器地址(Server)、数据库名(Database)、认证方式(User Id/Password或Integrated Security=True)组成;
TrustServerCertificate=True:用于本地开发(跳过SSL证书验证,生产环境不建议用);
注意:连接字符串不要硬编码到代码里,生产环境应存在配置文件(如appsettings.json)中。
2.2 查询用户(DataReader)
using (SqlConnection conn = new SqlConnection(...)):using确保连接自动释放(即使抛出异常);
conn.Open():打开物理连接(ADO.NET默认用连接池,不会每次都新建连接);
SqlCommand:绑定SQL语句和连接,ExecuteReader()执行查询返回SqlDataReader;
SqlDataReader:只读向前的数据流,内存占用极小,适合大数据量查询;Read()方法移动到下一行,返回false表示无数据。
2.3 插入用户(参数化查询)
INSERT INTO ... OUTPUT INSERTED.Id:SQL Server特有的语法,返回插入的自增ID;
cmd.Parameters.AddWithValue("@Name", name):参数化查询,将用户输入作为参数传递,彻底防止SQL注入;
ExecuteScalar():执行SQL并返回第一行第一列的值(这里是自增ID)。
2.4 更新/删除用户
ExecuteNonQuery():执行插入/更新/删除操作,返回受影响的行数(用于判断操作是否成功);
参数化同样是重点,避免用户输入包含恶意SQL(比如id="1;DROP TABLE Users")。
2.5 DataAdapter填充DataSet
SqlDataAdapter:断开式连接的核心组件,自动处理连接的打开/关闭;
Fill(ds, "Users"):将查询结果填充到DataSet的Users表中;
DataSet:内存中的数据库,可离线修改数据(比如添加行、修改列),之后用Update()方法同步回数据库。 -
基础知识拓展
3.1 核心组件对比
组件 连接方式 用途 优点 缺点
SqlDataReader 连接式(需保持连接打开) 读取大数据量、只读数据 内存占用小、速度快 必须向前读取、无法修改数据
DataSet 断开式(填充后可关闭连接) 离线处理数据、多表关联 可修改、可离线、支持关系 内存占用大、速度慢
SqlCommand 连接式 执行SQL/存储过程 灵活、支持参数化 需手动管理连接
3.2 SQL注入的危害与防范
危害:用户输入包含恶意SQL语句,比如name="';DROP TABLE Users;--",会导致表被删除;
防范:
1.永远使用参数化查询(SqlCommand.Parameters);
2.避免字符串拼接SQL(如string sql = "INSERT INTO Users VALUES ('" + name + "')");
3.限制数据库用户权限(比如只读用户无法执行DROP操作)。
3.3 连接池机制
ADO.NET默认开启连接池,缓存已打开的连接;
using释放连接时,连接会被放回池里(而非关闭物理连接),下次调用Open()直接复用;
最佳实践:不要长时间占用连接(比如查询完立即关闭),避免连接池耗尽。
3.4 事务处理
当需要多个操作原子性(要么全成功,要么全失败)时,用SqlTransaction:
csharp
using (SqlConnection conn = new SqlConnection(...))
{
conn.Open();
using (SqlTransaction tran = conn.BeginTransaction())
{
try
{
// 执行操作1
SqlCommand cmd1 = new SqlCommand("INSERT ...", conn, tran);
cmd1.ExecuteNonQuery();
// 执行操作2
SqlCommand cmd2 = new SqlCommand("UPDATE ...", conn, tran);
cmd2.ExecuteNonQuery();
tran.Commit(); // 提交事务
}
catch
{
tran.Rollback(); // 回滚事务
throw;
}
}
}
3.5 跨数据库适配
ADO.NET支持多种数据库,只需替换对应的组件:
| 数据库 | 组件前缀 | NuGet包 |
|---|---|---|
| SQL Server | Sql | 内置(System.Data.SqlClient) |
| MySQL | MySql | MySql.Data |
| Oracle | Oracle | Oracle.ManagedDataAccess.Core |
| PostgreSQL | Npgsql | Npgsql |
-
常见问题与解决
4.1 连接泄漏
原因:忘记释放连接(比如没写using,或conn.Close()被注释);
解决:所有连接/Command/DataReader都用using包裹,自动释放资源。
4.2 SQL注入
原因:用字符串拼接SQL(比如"WHERE Name='" + name + "'");
解决:强制使用参数化查询,禁用任何字符串拼接的SQL。
4.3 连接字符串错误
症状:抛出SqlException(比如“无法连接到服务器”);
检查:服务器名是否正确、数据库是否存在、账号密码是否正确、防火墙是否开放1433端口。
4.4 DataReader未关闭
症状:连接被占用,后续操作无法打开连接;
解决:SqlDataReader必须用using包裹,或手动调用Close()。 -
最佳实践
1.用using释放资源:连接、Command、DataReader、DataAdapter都要放在using里;
2.参数化查询:杜绝字符串拼接SQL,防止注入;
3.短连接优先:查询/操作完成后立即释放连接,避免占用连接池;
4.大数据用DataReader:处理百万级数据时,DataReader比DataSet快10倍以上;
5.配置文件存连接字符串:生产环境用IConfiguration读取appsettings.json中的连接字符串;
6.**避免Select ***:只查询需要的列,减少网络传输和内存占用。
总结
ADO.NET是C#操作数据库的基础,核心要点:
连接用SqlConnection,必须用using释放;
执行SQL用SqlCommand,参数化查询是底线;
读取数据选DataReader(大数据)或DataSet(离线处理);
永远防止SQL注入,参数化是唯一可靠的方法。
掌握ADO.NET能让你直接操作数据库,是后端开发的必备技能(即使现在流行EF Core,底层也是ADO.NET)。
(本节完)
下一章:EF Core入门:ORM框架的使用
(用EF Core简化数据库操作,告别手写SQL)
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49391.html
最新更新
EF Core入门:ORM映射与查询
ADO.NET基础:连接数据库与执行SQL
自定义特性
反射:动态获取类型信息
异步方法定义与调用
目录操作与路径处理
文件读写:StreamReader/StreamWriter
try-catch-finally异常捕获
try-catch-finally异常捕获
队列(Queue)与栈(Stack)
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比










