VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 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注入。

  1. 实例代码:完整的数据库操作流程
    以下实例实现连接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;
	}
	}
  1. 逐行讲解
    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()方法同步回数据库。
  2. 基础知识拓展
    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
  1. 常见问题与解决
    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()。
  2. 最佳实践
    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


相关教程