SqlHelper 是 .NET Framework 時代廣泛使用的 ADO.NET 封裝庫,旨在簡化 SqlConnection、SqlCommand、SqlDataReader 的樣板代碼,提供執行標量、非查詢、實體映射、列表查詢等高階 API。本文深度剖析其核心實現,並指出現代 .NET Core/.NET 5+ 環境下的改進方向。
private static SqlConnection _con = null;
public static string constr = "數據庫連接字符串";
public static SqlConnection Con {
get {
if (_con == null) _con = new SqlConnection();
if (string.IsNullOrEmpty(_con.ConnectionString))
_con.ConnectionString = constr;
return _con;
}
set => _con = value;
}
嚴重問題:靜態 SqlConnection 共享於所有線程,非線程安全。併發調用時會拋出「連接狀態無效」異常。正確做法是每次操作創建新連接,依賴連接池復用底層物理連接。
public static int ExecuteScalar(string cmdText, CommandType cmdType, params SqlParameter[] paras) {
using (var conn = new SqlConnection(constr))
using (var cmd = new SqlCommand(cmdText, conn)) {
cmd.CommandType = cmdType;
if (paras != null) cmd.Parameters.AddRange(paras);
conn.Open();
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
public static int ExecuteNonQuery(string cmdText, CommandType cmdType, params SqlParameter[] paras) {
using (var conn = new SqlConnection(constr))
using (var cmd = new SqlCommand(cmdText, conn)) {
cmd.CommandType = cmdType;
if (paras != null) cmd.Parameters.AddRange(paras);
conn.Open();
return cmd.ExecuteNonQuery();
}
}
優點:using 確保連接釋放,參數化防 SQL 注入。建議:移除靜態連接,改為方法內 `new SqlConnection()`。
private static T ExecuteDataReader<T>(IDataReader reader) {
T obj = Activator.CreateInstance<T>(); // 需無參構造函數
var props = typeof(T).GetProperties();
foreach (var prop in props) {
for (int i = 0; i < reader.FieldCount; i++) {
if (reader.GetName(i).Equals(prop.Name, StringComparison.OrdinalIgnoreCase)) {
var val = reader[prop.Name];
if (val != null && val != DBNull.Value)
prop.SetValue(obj, val);
break;
}
}
}
return obj;
}
public static T ExecuteEntity<T>(string cmdText, CommandType cmdType, params SqlParameter[] paras) {
using (var conn = new SqlConnection(constr))
using (var cmd = new SqlCommand(cmdText, conn)) {
cmd.CommandType = cmdType;
if (paras != null) cmd.Parameters.AddRange(paras);
conn.Open();
using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) {
return reader.Read() ? ExecuteDataReader<T>(reader) : default;
}
}
}
public static List<T> ExecuteList<T>(string cmdText, CommandType cmdType, params SqlParameter[] paras) {
var list = new List<T>();
using (var conn = new SqlConnection(constr))
using (var cmd = new SqlCommand(cmdText, conn)) {
cmd.CommandType = cmdType;
if (paras != null) cmd.Parameters.AddRange(paras);
conn.Open();
using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) {
while (reader.Read()) list.Add(ExecuteDataReader<T>(reader));
}
}
return list;
}
| 優點 | 缺點 |
|---|---|
| 零配置,約定優於配置 (屬性名=列名) | 反射開銷大:GetProperties/SetValue 比直接賦值慢 10-100 倍 |
| 泛型強類型,編譯期檢查 | 不支援嵌套對象、異名映射、類型轉換邏輯 |
| 代碼極簡,快速開發 | 每次查詢重複反射,無緩存機制 |
// Program.cs / Startup.cs
builder.Services.AddScoped<ISqlHelper, SqlHelper>();
builder.Services.Configure<DbOptions>(cfg.GetSection("ConnectionStrings"));
public interface ISqlHelper {
Task<int> ExecuteScalarAsync(string sql, CommandType type, params SqlParameter[] p);
Task<int> ExecuteNonQueryAsync(string sql, CommandType type, params SqlParameter[] p);
Task<T> QueryFirstOrDefaultAsync<T>(string sql, CommandType type, params SqlParameter[] p);
Task<IEnumerable<T>> QueryAsync<T>(string sql, CommandType type, params SqlParameter[] p);
}
public class DapperHelper : ISqlHelper {
private readonly string _connStr;
public DapperHelper(IOptions<DbOptions> opt) => _connStr = opt.Value.Default;
private IDbConnection CreateConnection() => new SqlConnection(_connStr);
public async Task<IEnumerable<T>> QueryAsync<T>(string sql, CommandType type, params SqlParameter[] p) {
using var conn = CreateConnection();
var dp = new DynamicParameters();
if (p != null) foreach (var param in p) dp.Add(param.ParameterName, param.Value);
return await conn.QueryAsync<T>(sql, dp, commandType: type);
}
// 其他方法類似...
}
SqlHelper 承載了 .NET 早期數據訪問的簡化智慧,但隨著生態演進,拥抱現代化工具鏈才能釋放 .NET 平台的真正性能與開發效率。