C# SqlServerHelper 助手类 调用示例

一个针对 .NET Framework 4.7.2 优化过的经典 SqlServerHelper 助手类。它基于 ADO.NET 原生开发,完整实现了防 SQL 注入的参数化查询、事务处理、存储过程调用以及资源自动释放(using 结构)。

SqlServerHelper.cs 源码

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;

public class SqlServerHelper
{
    // 从配置文件中读取连接字符串,如果读取不到则使用备用字符串
    private static readonly string connString = ConfigurationManager.ConnectionStrings["SqlConn"]?.ConnectionString 
        ?? "Data Source=.;Initial Catalog=YourDB;Integrated Security=True;";

    #region ExecuteNonQuery (增、删、改)

    /// <summary>
    /// 执行增删改语句,返回受影响的行数
    /// </summary>
    public static int ExecuteNonQuery(string cmdText, params SqlParameter[] parameters)
    {
        using (SqlConnection conn = new SqlConnection(connString))
        {
            using (SqlCommand cmd = new SqlCommand())
            {
                PrepareCommand(cmd, conn, null, CommandType.Text, cmdText, parameters);
                int rows = cmd.ExecuteNonQuery();
                cmd.Parameters.Clear();
                return rows;
            }
        }
    }

    #endregion

    #region ExecuteScalar & Exists (查单值与存在性验证)

    /// <summary>
    /// 执行查询,返回结果集中的第一行第一列(object 类型)
    /// </summary>
    public static object ExecuteScalar(string cmdText, params SqlParameter[] parameters)
    {
        using (SqlConnection conn = new SqlConnection(connString))
        {
            using (SqlCommand cmd = new SqlCommand())
            {
                PrepareCommand(cmd, conn, null, CommandType.Text, cmdText, parameters);
                object val = cmd.ExecuteScalar();
                cmd.Parameters.Clear();
                return val;
            }
        }
    }

    /// <summary>
    /// 执行查询,返回强类型的结果(自动处理 DBNull 与类型转换)
    /// </summary>
    public static T ExecuteScalar<T>(string cmdText, params SqlParameter[] parameters)
    {
        object val = ExecuteScalar(cmdText, parameters);
        if (val == null || val == DBNull.Value)
        {
            return default(T);
        }

        // 处理 Nullable<T> (如 int?, DateTime? 等)
        Type type = typeof(T);
        if (Nullable.GetUnderlyingType(type) != null)
        {
            type = Nullable.GetUnderlyingType(type);
        }

        return (T)Convert.ChangeType(val, type);
    }

    /// <summary>
    /// 检查记录是否存在 (基于查询结果的第一行第一列是否为空)
    /// 建议 SQL 写法: SELECT 1 FROM Table WHERE ...
    /// </summary>
    public static bool Exists(string cmdText, params SqlParameter[] parameters)
    {
        object val = ExecuteScalar(cmdText, parameters);
        // 只要结果不为 null 且不为 DBNull,即代表存在记录
        return val != null && val != DBNull.Value;
    }

    #endregion

    #region ExecuteReader (高性能流式读取)

    /// <summary>
    /// 执行查询,返回 SqlDataReader
    /// 注意:调用方必须使用 using() 包裹返回值,或手动 Close(),否则会导致数据库连接泄露!
    /// </summary>
    public static SqlDataReader ExecuteReader(string cmdText, params SqlParameter[] parameters)
    {
        // 这里千万不能用 using(SqlConnection conn = ...),否则连接关闭后 Reader 将无法读取数据
        SqlConnection conn = new SqlConnection(connString);
        SqlCommand cmd = new SqlCommand();
        try
        {
            PrepareCommand(cmd, conn, null, CommandType.Text, cmdText, parameters);
            
            // CommandBehavior.CloseConnection 的作用:
            // 告诉 Reader:当你被关闭 (Reader.Close/Dispose) 时,连同背后的 SqlConnection 一起关闭!
            SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
            cmd.Parameters.Clear();
            return reader;
        }
        catch
        {
            // 如果在执行过程中发生异常,必须手动关闭连接并释放资源
            conn.Close();
            cmd.Dispose();
            throw;
        }
    }

    #endregion

    #region ExecuteDataTable & ExecuteDataSet (查表格/多表)

    /// <summary>
    /// 执行查询,返回 DataTable
    /// </summary>
    public static DataTable ExecuteDataTable(string cmdText, params SqlParameter[] parameters)
    {
        using (SqlConnection conn = new SqlConnection(connString))
        {
            using (SqlCommand cmd = new SqlCommand())
            {
                PrepareCommand(cmd, conn, null, CommandType.Text, cmdText, parameters);
                using (SqlDataAdapter da = new SqlDataAdapter(cmd))
                {
                    DataTable dt = new DataTable();
                    da.Fill(dt);
                    cmd.Parameters.Clear();
                    return dt;
                }
            }
        }
    }

    /// <summary>
    /// 执行查询,返回 DataSet(多表结果集)
    /// </summary>
    public static DataSet ExecuteDataSet(string cmdText, params SqlParameter[] parameters)
    {
        using (SqlConnection conn = new SqlConnection(connString))
        {
            using (SqlCommand cmd = new SqlCommand())
            {
                PrepareCommand(cmd, conn, null, CommandType.Text, cmdText, parameters);
                using (SqlDataAdapter da = new SqlDataAdapter(cmd))
                {
                    DataSet ds = new DataSet();
                    da.Fill(ds);
                    cmd.Parameters.Clear();
                    return ds;
                }
            }
        }
    }

    #endregion

    #region 存储过程支持 (Stored Procedure)

    /// <summary>
    /// 执行存储过程,返回 DataTable
    /// </summary>
    public static DataTable ExecuteStoredProcedure(string procName, params SqlParameter[] parameters)
    {
        using (SqlConnection conn = new SqlConnection(connString))
        {
            using (SqlCommand cmd = new SqlCommand())
            {
                PrepareCommand(cmd, conn, null, CommandType.StoredProcedure, procName, parameters);
                using (SqlDataAdapter da = new SqlDataAdapter(cmd))
                {
                    DataTable dt = new DataTable();
                    da.Fill(dt);
                    cmd.Parameters.Clear();
                    return dt;
                }
            }
        }
    }

    #endregion

    #region 事务支持 (Transaction)

    /// <summary>
    /// 执行多条 SQL 语句(带事务),全部成功返回 true,任一失败自动回滚并抛出异常
    /// </summary>
    public static bool ExecuteTransaction(List<SqlCommandInfo> commandList)
    {
        using (SqlConnection conn = new SqlConnection(connString))
        {
            conn.Open();
            using (SqlTransaction trans = conn.BeginTransaction())
            {
                using (SqlCommand cmd = new SqlCommand())
                {
                    try
                    {
                        foreach (var cmdInfo in commandList)
                        {
                            PrepareCommand(cmd, conn, trans, CommandType.Text, cmdInfo.CommandText, cmdInfo.Parameters);
                            cmd.ExecuteNonQuery();
                            cmd.Parameters.Clear(); // 每次循环前清空参数,防止冲突
                        }
                        trans.Commit();
                        return true;
                    }
                    catch (Exception)
                    {
                        trans.Rollback();
                        throw; // 向上抛出异常,便于调用方捕捉错误日志
                    }
                }
            }
        }
    }

    #endregion

    #region 内部辅助方法 (参数预处理)

    /// <summary>
    /// 构建并准备好 SqlCommand 状态
    /// </summary>
    private static void PrepareCommand(SqlCommand cmd, SqlConnection conn, SqlTransaction trans, CommandType cmdType, string cmdText, SqlParameter[] cmdParms)
    {
        if (conn.State != ConnectionState.Open)
            conn.Open();

        cmd.Connection = conn;
        cmd.CommandText = cmdText;
        cmd.CommandType = cmdType;

        if (trans != null)
            cmd.Transaction = trans;

        if (cmdParms != null)
        {
            foreach (SqlParameter parm in cmdParms)
            {
                // 优雅处理 C# 的 null 转化为数据库的 DBNull.Value
                if (parm.Value == null)
                    parm.Value = DBNull.Value;
                
                cmd.Parameters.Add(parm);
            }
        }
    }

    #endregion
}

/// <summary>
/// 专用于事务批量执行的命令结构体
/// </summary>
public class SqlCommandInfo
{
    public string CommandText { get; set; }
    public SqlParameter[] Parameters { get; set; }

    public SqlCommandInfo(string cmdText, SqlParameter[] parameters)
    {
        this.CommandText = cmdText;
        this.Parameters = parameters;
    }
}


App.config / Web.config 配置
在使用该类前,请确保在配置文件中添加了连接字符串:

<connectionStrings>
    <add name="SqlConn" connectionString="Data Source=你的服务器地址;Initial Catalog=数据库名;User ID=用户名;Password=密码;" providerName="System.Data.SqlClient" />
</connectionStrings>


常用功能调用示例
以下是该助手类在日常开发中的具体使用方式:

插入/更新/删除 数据 (ExecuteNonQuery)

string sql = "INSERT INTO Users (UserName, Age, CreateTime) VALUES (@Name, @Age, @CreateTime)";
SqlParameter[] paras = {
    new SqlParameter("@Name", "张三"),
    new SqlParameter("@Age", 25),
    new SqlParameter("@CreateTime", DateTime.Now)
};

int rows = SqlServerHelper.ExecuteNonQuery(sql, paras);
Console.WriteLine($"成功插入 {rows} 行数据");


查询单个值 (ExecuteScalar)

string sql = "SELECT COUNT(1) FROM Users WHERE Age > @Age";
SqlParameter[] paras = {
    new SqlParameter("@Age", 18)
};

int count = Convert.ToInt32(SqlServerHelper.ExecuteScalar(sql, paras));


查询单个值 ExecuteScalar<T>
再也不用每次都在业务代码里写烦人的 Convert.ToInt32() 或者判断 DBNull 了。

string sql = "SELECT MAX(Age) FROM Users";

// 自动转换并返回强类型的 int,如果表里没数据会返回 default(int) 也就是 0
int maxAge = SqlServerHelper.ExecuteScalar<int>(sql);

string sql2 = "SELECT UserName FROM Users WHERE Id = @Id";

// 自动转换为 string,找不到返回 null
string name = SqlServerHelper.ExecuteScalar<string>(sql2, new SqlParameter("@Id", 1));


检查记录是否存在 Exists
最适合用来做注册时验证用户名是否重复、或者操作前验证数据是否存在。

// 技巧:写 SELECT 1 性能最高,不要写 SELECT *
string sql = "SELECT 1 FROM Users WHERE UserName = @Name";
SqlParameter[] paras = { new SqlParameter("@Name", "Admin") };

if (SqlServerHelper.Exists(sql, paras))
{
    Console.WriteLine("用户名已存在!");
}


使用 ExecuteReader 极速读取大数据集!切记必须用 using 包裹。

string sql = "SELECT Id, UserName FROM Users WHERE Status = 1";

// 必须在 UI/BLL 层套上 using,这样 Reader 读完被释放时,连带关闭底层的 SqlConnection
using (SqlDataReader reader = SqlServerHelper.ExecuteReader(sql))
{
    while (reader.Read())
    {
        int id = reader.GetInt32(0);
        string name = reader.GetString(1);
        Console.WriteLine($"ID: {id}, Name: {name}");
    }
}


获取表格数据 (ExecuteDataTable)

string sql = "SELECT * FROM Users WHERE UserName LIKE @Name + '%'";
SqlParameter[] paras = {
    new SqlParameter("@Name", "")
};

DataTable dt = SqlServerHelper.ExecuteDataTable(sql, paras);
foreach (DataRow row in dt.Rows)
{
    Console.WriteLine(row["UserName"].ToString());
}


执行多条 SQL 事务 (ExecuteTransaction)

var list = new List<SqlCommandInfo>();

// 语句 1
string sql1 = "UPDATE Accounts SET Balance = Balance - @Amount WHERE AccountId = @Id1";
SqlParameter[] para1 = { new SqlParameter("@Amount", 100), new SqlParameter("@Id1", 1) };
list.Add(new SqlCommandInfo(sql1, para1));

// 语句 2
string sql2 = "UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountId = @Id2";
SqlParameter[] para2 = { new SqlParameter("@Amount", 100), new SqlParameter("@Id2", 2) };
list.Add(new SqlCommandInfo(sql2, para2));

try
{
    bool isSuccess = SqlServerHelper.ExecuteTransaction(list);
    if (isSuccess) Console.WriteLine("转账成功!");
}
catch (Exception ex)
{
    Console.WriteLine($"转账失败,事务已安全回滚。错误详情: {ex.Message}");
}


ExecuteDataSet 最大的杀手锏在于“一次数据库往返,获取多个结果集”。
当你需要在同一个页面或同一个业务逻辑中,同时查询多张不相关的表(例如:同时查出一个用户的“基本信息”和他的“近期订单列表”)时,它是最高效的选择。

场景说明
我们需要在一个“用户详情页”展示数据。为了减少与数据库的网络交互次数,我们编写了一段包含两个 SELECT 的批量 SQL 语句,一次性把用户信息和订单信息全部抓取回来。

using System;
using System.Data;
using System.Data.SqlClient;

public class UserService
{
    public void PrintUserDetailAndOrders(int targetUserId)
    {
        // 巧妙利用批量 SQL 语句,用分号或直接换行隔开多个 SELECT 
        string sql = @"
            -- 第 1 个结果集:获取用户的基本资料
            SELECT UserId, UserName, Email, Status 
            FROM Users 
            WHERE UserId = @UserId;

            -- 第 2 个结果集:获取该用户的最近 5 笔订单记录
            SELECT TOP 5 OrderId, OrderDate, TotalAmount 
            FROM Orders 
            WHERE UserId = @UserId 
            ORDER BY OrderDate DESC;
        ";

        SqlParameter[] paras = {
            new SqlParameter("@UserId", targetUserId)
        };

        // 调用助手类,返回包含多张表的 DataSet
        DataSet ds = SqlServerHelper.ExecuteDataSet(sql, paras);

        // 防御性编程:确保 DataSet 不为空,且确实包含我们要的 2 张表
        if (ds != null && ds.Tables.Count >= 2)
        {
            // --- 处理第一张表 (索引为 0) : 用户信息 ---
            DataTable dtUser = ds.Tables[0];
            if (dtUser.Rows.Count > 0)
            {
                DataRow userRow = dtUser.Rows[0];
                Console.WriteLine("=== 用户基本信息 ===");
                Console.WriteLine($"用户ID: {userRow["UserId"]}");
                Console.WriteLine($"用户名: {userRow["UserName"]}");
                Console.WriteLine($"邮  箱: {userRow["Email"]}");
            }
            else
            {
                Console.WriteLine("未找到该用户。");
                return; // 用户不存在,直接退出
            }

            Console.WriteLine("\n-------------------------\n");

            // --- 处理第二张表 (索引为 1) : 订单列表 ---
            DataTable dtOrders = ds.Tables[1];
            Console.WriteLine($"=== 最近订单 ({dtOrders.Rows.Count} 笔) ===");
            
            if (dtOrders.Rows.Count > 0)
            {
                foreach (DataRow row in dtOrders.Rows)
                {
                    // 强转类型或直接格式化输出
                    int orderId = Convert.ToInt32(row["OrderId"]);
                    DateTime orderDate = Convert.ToDateTime(row["OrderDate"]);
                    decimal amount = Convert.ToDecimal(row["TotalAmount"]);

                    Console.WriteLine($"订单号: {orderId} | 日期: {orderDate:yyyy-MM-dd HH:mm} | 金额: ¥{amount}");
                }
            }
            else
            {
                Console.WriteLine("该用户暂无订单记录。");
            }
        }
        else
        {
            Console.WriteLine("数据查询异常,未返回预期的结果集。");
        }
    }
}

发表评论