ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C# ADO.NET 数据库编程实战:从Connection到DBHelper的完整指南

C# ADO.NET 数据库编程实战:从Connection到DBHelper的完整指南 前言想象一下你辛辛苦苦在SQL Server里建好了表、设好了约束、插入了测试数据。现在问题来了——C#程序怎么才能跟数据库对话ADO.NET就是答案。它是.NET框架中负责数据访问的类库集是程序与数据库之间的桥梁。无论是用户登录、数据查询还是订单提交、报表导出都离不开ADO.NET。核心理解ADO.NET 程序 ⇄ 数据库 的“翻译官”。你用C#写一句“把张三的工资改成5000”ADO.NET帮你翻译成SQL发给数据库再把执行结果带回来。一、ADO.NET的两大工作模式ADO.NET支持两种数据访问模式理解它们的区别是选对工具的前提1.1 连接模式Connected Mode代表对象SqlConnectionSqlCommandSqlDataReader特点读取数据时必须保持数据库连接打开数据以只读、只进的流式方式获取适用场景大数据量查询、报表导出、只需读取一次的数据1.2 断开模式Disconnected Mode代表对象SqlDataAdapterDataSet/DataTable特点一次性把数据拉到内存中然后断开连接在内存中自由操作数据适用场景需要反复修改数据、离线操作、数据绑定到UI控件对比维度连接模式DataReader断开模式DataSet数据库连接一直打开填充后关闭数据方向只读、只进可读可写、可前后移动内存占用小逐行流式大整表加载适用场景大数据量查询数据编辑、UI绑定二、五大核心对象详解ADO.NET的核心架构建立在“托管提供程序模型”之上抽象为五类基础对象2.1 SqlConnection——数据库连接的“钥匙”SqlConnection负责建立应用程序与数据库之间的连接通道。连接字符串的构成// SQL Server 连接字符串 string connStr Serverlocalhost;DatabaseMySchool;User Idsa;Password123456;; // 或使用 SqlConnectionStringBuilder更安全、可读性更好 SqlConnectionStringBuilder builder new SqlConnectionStringBuilder(); builder.DataSource localhost; builder.InitialCatalog MySchool; builder.UserID sa; builder.Password 123456; string connStr builder.ConnectionString;使用步骤using System.Data.SqlClient; // 1. 定义连接字符串 string connStr Server.;DatabaseMySchool;User Idsa;Password123456;; // 2. 创建连接对象 SqlConnection conn new SqlConnection(connStr); try { // 3. 打开连接 conn.Open(); Console.WriteLine($连接状态{conn.State}); // Open // 4. 执行数据库操作... } catch (Exception ex) { Console.WriteLine($连接失败{ex.Message}); } finally { // 5. 关闭连接释放资源 if (conn.State ConnectionState.Open) conn.Close(); }⭐ 最佳实践使用using语句自动释放资源// using 会自动调用 Dispose() 关闭连接无需手动 Close using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); // 操作数据库... } // 自动释放连接池Connection Pooling.NET默认启用连接池复用已打开的连接避免频繁创建销毁连接的开销。只要使用using正确释放连接连接池会自动工作。2.2 SqlCommand——执行SQL的“嘴巴”SqlCommand负责把SQL语句“说”给数据库听。三种核心执行方法方法返回值适用场景ExecuteNonQuery()受影响的行数intINSERT、UPDATE、DELETEExecuteScalar()第一行第一列的值objectCOUNT、SUM等聚合查询ExecuteReader()SqlDataReader对象SELECT查询返回多行数据示例1ExecuteNonQuery——增删改操作string connStr Server.;DatabaseMySchool;User Idsa;Password123456;; using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); // 插入数据 string sql INSERT INTO Student (Name, Gender, Age) VALUES (张三, 男, 20); SqlCommand cmd new SqlCommand(sql, conn); int rows cmd.ExecuteNonQuery(); Console.WriteLine($受影响的行数{rows}); // 输出1 }示例2ExecuteScalar——获取单值using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); string sql SELECT COUNT(*) FROM Student; SqlCommand cmd new SqlCommand(sql, conn); int count Convert.ToInt32(cmd.ExecuteScalar()); Console.WriteLine($学生总数{count}); }示例3ExecuteReader——查询多行数据using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); string sql SELECT Id, Name, Gender, Age FROM Student; SqlCommand cmd new SqlCommand(sql, conn); using (SqlDataReader reader cmd.ExecuteReader()) { Console.WriteLine(ID\t姓名\t性别\t年龄); while (reader.Read()) // 每调用一次Read()前进到下一行 { // 方式1通过列名访问 int id Convert.ToInt32(reader[Id]); string name reader[Name].ToString(); string gender reader[Gender].ToString(); int age Convert.ToInt32(reader[Age]); Console.WriteLine(${id}\t{name}\t{gender}\t{age}); } } // reader自动关闭 }2.3 SqlParameter——防止SQL注入的“盾牌”⚠️ 危险做法字符串拼接// 千万不要这样写有SQL注入风险 string name 张三; DROP TABLE Student; --; string sql $SELECT * FROM Student WHERE Name {name}; // 执行后Student表被删除了✅ 安全做法参数化查询using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); string sql SELECT * FROM Student WHERE Name Name AND Age Age; SqlCommand cmd new SqlCommand(sql, conn); // 添加参数推荐显式指定类型[reference:12] cmd.Parameters.Add(new SqlParameter(Name, SqlDbType.NVarChar, 50) { Value 张三 }); cmd.Parameters.Add(new SqlParameter(Age, SqlDbType.Int) { Value 18 }); using (SqlDataReader reader cmd.ExecuteReader()) { while (reader.Read()) { Console.WriteLine(reader[Name]); } } }参数化查询的优势防止SQL注入参数值不会被当作SQL代码执行提高性能SQL语句被预编译执行计划可重用类型安全自动处理数据类型转换2.4 DataSet与DataTable——内存中的“数据库”DataSet是内存中的数据库可以包含多个DataTable每个DataTable相当于一张表。创建和使用DataSetusing System.Data; // 1. 创建DataSet DataSet ds new DataSet(SchoolDB); // 2. 创建DataTable并添加到DataSet DataTable studentTable new DataTable(Student); ds.Tables.Add(studentTable); // 3. 定义列 studentTable.Columns.Add(Id, typeof(int)); studentTable.Columns.Add(Name, typeof(string)); studentTable.Columns.Add(Age, typeof(int)); // 4. 添加数据行 studentTable.Rows.Add(1, 张三, 20); studentTable.Rows.Add(2, 李四, 22); studentTable.Rows.Add(3, 王五, 19); // 5. 查询数据LINQ风格 DataRow[] filteredRows studentTable.Select(Age 20); foreach (DataRow row in filteredRows) { Console.WriteLine(row[Name]); } // 6. 修改数据 DataRow firstRow studentTable.Rows[0]; firstRow[Name] 张更新; // 7. 删除数据标记删除需要AcceptChanges才真正移除 studentTable.Rows[1].Delete(); // 8. 遍历数据 foreach (DataRow row in studentTable.Rows) { Console.WriteLine(${row[Id]}\t{row[Name]}\t{row[Age]}); }DataTable的RowState每行数据都有状态标记用于跟踪变更Unchanged未修改Added新增Modified已修改Deleted已删除2.5 SqlDataAdapter——连接与断开的“桥梁”SqlDataAdapter是连接模式和断开模式之间的桥梁。它负责Fill()从数据库填充数据到DataSetUpdate()把DataSet中的变更批量更新回数据库using (SqlConnection conn new SqlConnection(connStr)) { // 1. 创建适配器 string sql SELECT * FROM Student; SqlDataAdapter adapter new SqlDataAdapter(sql, conn); // 2. 填充DataSet自动打开和关闭连接 DataSet ds new DataSet(); adapter.Fill(ds, Student); // 填充到名为Student的DataTable // 3. 在内存中操作数据此时连接已关闭 DataTable table ds.Tables[Student]; foreach (DataRow row in table.Rows) { Console.WriteLine(row[Name]); } // 4. 修改数据 table.Rows[0][Name] 更新后的名字; // 5. 创建CommandBuilder自动生成增删改命令简单场景 SqlCommandBuilder builder new SqlCommandBuilder(adapter); // 6. 批量更新回数据库 int rowsUpdated adapter.Update(ds, Student); Console.WriteLine($更新了{rowsUpdated}行); }Adapter的工作原理SelectCommand负责查询数据Fill时使用InsertCommand负责插入数据Update时使用UpdateCommand负责更新数据Update时使用DeleteCommand负责删除数据Update时使用三、实战案例用户登录验证综合运用上述知识实现一个安全的用户登录功能public class LoginService { private readonly string _connectionString; public LoginService(string connectionString) { _connectionString connectionString; } public bool Login(string username, string password) { // 使用参数化查询防止SQL注入 string sql SELECT COUNT(*) FROM Users WHERE UserName UserName AND Password Password; using (SqlConnection conn new SqlConnection(_connectionString)) { conn.Open(); SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.Add(new SqlParameter(UserName, SqlDbType.NVarChar, 50) { Value username }); cmd.Parameters.Add(new SqlParameter(Password, SqlDbType.NVarChar, 50) { Value password }); int count Convert.ToInt32(cmd.ExecuteScalar()); return count 0; } } // 获取用户详细信息使用DataReader public User GetUser(string username) { string sql SELECT Id, UserName, Email, CreateTime FROM Users WHERE UserName UserName; using (SqlConnection conn new SqlConnection(_connectionString)) { conn.Open(); SqlCommand cmd new SqlCommand(sql, conn); cmd.Parameters.AddWithValue(UserName, username); using (SqlDataReader reader cmd.ExecuteReader()) { if (reader.Read()) { return new User { Id Convert.ToInt32(reader[Id]), UserName reader[UserName].ToString(), Email reader[Email].ToString(), CreateTime Convert.ToDateTime(reader[CreateTime]) }; } return null; } } } } public class User { public int Id { get; set; } public string UserName { get; set; } public string Email { get; set; } public DateTime CreateTime { get; set; } }四、事务处理——保证数据一致性在涉及金额、库存等敏感操作时必须使用事务确保一组操作要么全部成功要么全部回滚。public class BankService { private readonly string _connectionString; public bool Transfer(int fromAccount, int toAccount, decimal amount) { using (SqlConnection conn new SqlConnection(_connectionString)) { conn.Open(); // 开始事务 SqlTransaction transaction conn.BeginTransaction(); try { // 扣款 string sql1 UPDATE Account SET Balance Balance - Amount WHERE Id FromId; SqlCommand cmd1 new SqlCommand(sql1, conn, transaction); cmd1.Parameters.AddWithValue(Amount, amount); cmd1.Parameters.AddWithValue(FromId, fromAccount); cmd1.ExecuteNonQuery(); // 加款 string sql2 UPDATE Account SET Balance Balance Amount WHERE Id ToId; SqlCommand cmd2 new SqlCommand(sql2, conn, transaction); cmd2.Parameters.AddWithValue(Amount, amount); cmd2.Parameters.AddWithValue(ToId, toAccount); cmd2.ExecuteNonQuery(); // 全部成功提交事务 transaction.Commit(); return true; } catch (Exception ex) { // 任何一步失败回滚所有操作 transaction.Rollback(); Console.WriteLine($转账失败{ex.Message}); return false; } } } }事务的ACID特性原子性要么全部成功要么全部失败一致性事务前后数据状态保持一致隔离性并发事务互不干扰持久性提交后数据永久保存隔离级别IsolationLevel// 可根据业务需求指定隔离级别 SqlTransaction transaction conn.BeginTransaction(IsolationLevel.ReadCommitted);ReadUncommitted允许脏读性能最高安全性最低ReadCommitted防止脏读SQL Server默认RepeatableRead防止脏读和不可重复读Serializable最高级别完全隔离性能最低五、DBHelper封装——让代码更优雅在实际项目中每次都重复写Connection、Command、Parameter的创建代码既冗余又容易出错。封装一个DBHelper工具类是必经之路。5.1 基础版DBHelperusing System.Data; using System.Data.SqlClient; public class DBHelper { private readonly string _connectionString; public DBHelper(string connectionString) { _connectionString connectionString; } // 执行增删改返回受影响行数 public int ExecuteNonQuery(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(_connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteNonQuery(); } } // 执行查询返回单个值 public object ExecuteScalar(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(_connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteScalar(); } } // 执行查询返回DataTable public DataTable ExecuteDataTable(string sql, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(_connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); SqlDataAdapter adapter new SqlDataAdapter(cmd); DataTable dt new DataTable(); adapter.Fill(dt); return dt; } } // 执行查询返回SqlDataReader注意调用方负责关闭 public SqlDataReader ExecuteReader(string sql, params SqlParameter[] parameters) { SqlConnection conn new SqlConnection(_connectionString); SqlCommand cmd new SqlCommand(sql, conn); if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); // CommandBehavior.CloseConnectionReader关闭时自动关闭连接 return cmd.ExecuteReader(CommandBehavior.CloseConnection); } }5.2 使用DBHelperclass Program { static void Main(string[] args) { string connStr Server.;DatabaseMySchool;User Idsa;Password123456;; DBHelper db new DBHelper(connStr); // 1. 查询用户登录 Console.Write(请输入用户名); string username Console.ReadLine(); Console.Write(请输入密码); string password Console.ReadLine(); string sql SELECT COUNT(*) FROM Users WHERE UserName UserName AND Password Password; SqlParameter[] pars new SqlParameter[] { new SqlParameter(UserName, SqlDbType.NVarChar, 50) { Value username }, new SqlParameter(Password, SqlDbType.NVarChar, 50) { Value password } }; int count Convert.ToInt32(db.ExecuteScalar(sql, pars)); if (count 0) Console.WriteLine(登录成功); else Console.WriteLine(用户名或密码错误); // 2. 查询所有学生返回DataTable DataTable dt db.ExecuteDataTable(SELECT * FROM Student); foreach (DataRow row in dt.Rows) { Console.WriteLine(${row[Id]}\t{row[Name]}\t{row[Age]}); } // 3. 插入新学生 string insertSql INSERT INTO Student (Name, Gender, Age) VALUES (Name, Gender, Age); SqlParameter[] insertParams new SqlParameter[] { new SqlParameter(Name, SqlDbType.NVarChar, 50) { Value 赵六 }, new SqlParameter(Gender, SqlDbType.Char, 2) { Value 男 }, new SqlParameter(Age, SqlDbType.Int) { Value 23 } }; int rows db.ExecuteNonQuery(insertSql, insertParams); Console.WriteLine($插入了{rows}行数据); } }5.3 进阶版支持存储过程 事务public class DBHelperAdvanced { private readonly string _connectionString; public DBHelperAdvanced(string connectionString) { _connectionString connectionString; } // 支持存储过程 public int ExecuteNonQuery(string sql, CommandType commandType, params SqlParameter[] parameters) { using (SqlConnection conn new SqlConnection(_connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.CommandType commandType; if (parameters ! null) cmd.Parameters.AddRange(parameters); conn.Open(); return cmd.ExecuteNonQuery(); } } // 支持事务 public int ExecuteNonQueryWithTransaction(string sql, SqlParameter[] parameters, out string errorMessage) { errorMessage null; using (SqlConnection conn new SqlConnection(_connectionString)) { conn.Open(); SqlTransaction transaction conn.BeginTransaction(); try { using (SqlCommand cmd new SqlCommand(sql, conn, transaction)) { if (parameters ! null) cmd.Parameters.AddRange(parameters); int result cmd.ExecuteNonQuery(); transaction.Commit(); return result; } } catch (Exception ex) { transaction.Rollback(); errorMessage ex.Message; return -1; } } } }六、常见问题与最佳实践6.1 连接未关闭导致连接池耗尽// ❌ 错误忘记关闭连接 SqlConnection conn new SqlConnection(connStr); conn.Open(); // 操作数据库... // 没有Close()连接池中的连接一直被占用 // ✅ 正确使用using自动释放 using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); // 操作数据库... } // 自动释放6.2 SQL注入防护// ❌ 危险字符串拼接 string sql $SELECT * FROM Users WHERE Name {name}; // ✅ 安全参数化查询 string sql SELECT * FROM Users WHERE Name Name; cmd.Parameters.Add(new SqlParameter(Name, SqlDbType.NVarChar, 50) { Value name });6.3 选择合适的数据访问方式场景推荐方案大量数据只读查询SqlDataReader连接模式少量数据需要编辑DataSet DataAdapter断开模式单条记录的增删改ExecuteNonQuery获取总数/最大值ExecuteScalar批量数据同步DataAdapter.Update()多步操作需要一致性事务Transaction6.4 连接字符串放在哪里!-- App.config 或 Web.config -- connectionStrings add nameSchoolDb connectionStringServer.;DatabaseMySchool;User Idsa;Password123456; providerNameSystem.Data.SqlClient / /connectionStrings// 读取配置 string connStr ConfigurationManager.ConnectionStrings[SchoolDb].ConnectionString;七、总结这一周我们系统学习了ADO.NET数据库编程核心要点如下五大核心对象SqlConnection连接、SqlCommand命令、SqlDataReader只读流、SqlDataAdapter适配器、DataSet内存数据库两种工作模式连接模式DataReader高性能、只读、大数据量断开模式DataSet灵活编辑、离线操作、UI绑定安全第一永远使用参数化查询防止SQL注入资源管理使用using语句自动释放连接和Reader事务保证一致性在涉及多步操作时使用事务封装复用将重复的数据库操作封装到DBHelper中
返回列表