告别代码臃肿与SQL注入!C# 封装企业级万能 SqlHelper 类
·
数据库底层操作类(SqlHelper)
这篇博客是我经过不断踩坑、重构后提炼出的 终极版 SQL 数据库帮助类 (SqlHelper)
1、参数化防注入:抵御黑客攻击的基本素养。
2、异常向上抛出 (throw):底层不处理 UI 弹窗,报错直接抛给调用者(界面层)去捕获并展示。
3、严格的资源释放:全套 using 嵌套,绝不浪费连接池。
4、命名规范:摒弃拼音和随意命名,采用标准的企业级命名法。
核心代码:SqlHelper.cs 完整源码
/// <summary>
/// SQL Server 数据库操作帮助类 (工业级封装)
/// </summary>
public class SqlHelper
{
// 1. 获取全局配置的连接字符串
// 专业写法通常需要从 App.config 或 appsettings.json 中读取
private static readonly string connectionString = "";
/// <summary>
/// 多值查询 (返回 DataTable)
/// 适用场景:SELECT 查询多行多列,用于绑定 DataGridView、ListView 等
/// </summary>
/// <param name="sqlText">包含 @ 占位符的完整 SQL 查询语句</param>
/// <param name="parameters">与 SQL 语句中 @ 占位符对应的参数数组(可变参数,不传则无参查询)</param>
/// <returns>装载了查询结果的 DataTable 对象。即使查无数据,也会返回一个空的 DataTable 而不是 null</returns>
/// <exception cref="Exception">当底层数据库连接失败或 SQL 语法错误时,将底层的 SqlException 包装后向上抛出</exception>
public static DataTable ExecuteQuery(string sqlText, params SqlParameter[] parameters)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand(sqlText, conn))
{
// 工业级细节1:设置命令超时时间(默认通常是30秒,遇到复杂查询可适当调大)
cmd.CommandTimeout = 30;
if (parameters != null && parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}
using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
{
DataTable dt = new DataTable();
try
{
adapter.Fill(dt);
return dt;
}
catch (SqlException ex)
{
// 工业级细节2:捕获特定的数据库异常
// 在这里可以接入日志框架写入本地 txt 或系统日志
// Log.Error("数据库查询出错: " + sqlText, ex);
// 工业级细节3:将异常原封不动地抛给上层界面层!绝不隐瞒!
throw new Exception("底层执行查询报错:" + ex.Message, ex);
}
}
}
}
}
/// <summary>
/// 单值查询 (返回 Object)
/// 适用场景:SELECT COUNT(*), SELECT MAX(ID) 等只返回第一行第一列的查询
/// </summary>
/// <param name="sqlText">包含 @ 占位符的单值查询 SQL 语句</param>
/// <param name="parameters">与 SQL 语句中 @ 占位符对应的参数数组(可变参数)</param>
/// <returns>返回结果集的第一行第一列数据(object 类型)。若数据库对应字段为空 (DBNull),则返回 null</returns>
/// <exception cref="Exception">当底层数据库连接失败或 SQL 语法错误时抛出异常</exception>
public static object ExecuteScalar(string sqlText, params SqlParameter[] parameters)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand(sqlText, conn))
{
if (parameters != null && parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}
try
{
conn.Open();
object result = cmd.ExecuteScalar();
// 工业级细节4:处理 DBNull,数据库的空值在C#里是 DBNull.Value,不是 null
if (result == DBNull.Value)
{
return null;
}
return result;
}
catch (SqlException ex)
{
throw new Exception("底层执行单值查询报错:" + ex.Message, ex);
}
}
}
}
/// <summary>
/// 数据修改 (返回受影响的行数 int)
/// 适用场景:执行 INSERT(增), UPDATE(改), DELETE(删)操作
/// </summary>
/// <param name="sqlText">包含 @ 占位符的非查询类 SQL 语句</param>
/// <param name="parameters">与 SQL 语句中 @ 占位符对应的参数数组(可变参数)</param>
/// <returns>返回当前 SQL 操作成功影响的数据库行数</returns>
/// <exception cref="Exception">遇到主键冲突、外键约束失败或 SQL 语法错误时抛出异常,需由界面层捕获并提示</exception>
public static int ExecuteNonQuery(string sqlText, params SqlParameter[] parameters)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand(sqlText, conn))
{
if (parameters != null && parameters.Length > 0)
{
cmd.Parameters.AddRange(parameters);
}
try
{
conn.Open();
return cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
// 遇到主键冲突、外键约束等数据库报错,直接抛给界面层提示用户
throw new Exception("底层执行数据更新报错:" + ex.Message, ex);
}
}
}
}
}
💡 进阶小贴士:为什么要加 readonly?
细心的朋友可能会发现,我在声明连接字符串时加了一个 readonly(只读)关键字。这是干嘛用的?
它的核心作用是防篡改、防手抖。
在企业级开发中,代码动辄成千上万行。数据库连接字符串(connectionString)是整个数据交互的“命脉”。如果不加限制,万一哪天在写某个业务逻辑时不小心敲错代码,把它给重新赋值覆盖了,整个系统瞬间就会因为连不上数据库而瘫痪。
加上 readonly 后,就相当于给这个变量上了“死锁”:
只能在声明时或构造方法中赋值。
彻底锁死:程序运行期间,任何企图修改它的代码都会被编译器直接划红线拦截,连编译都过不去。
为什么不用 const(常量)?
因为 const 过于死板,必须在写代码时就把值写死。而 readonly 是“外柔内刚”,它允许我们在程序刚启动的那一瞬间,去动态读取外部的配置文件(比如 App.config),拿到值后再立刻锁死,完美契合工业级开发的配置读取需求!
更多推荐

所有评论(0)