企業級應用常面臨多種數據庫並存 (SQL Server, Oracle, MySQL, PostgreSQL) 及遷移需求。硬編碼特定 ADO.NET 提供程序 (SqlClient, OleDb, OracleClient) 會導致高耦合、難維護。本文展示如何結合 工廠模式 創建提供程序無關的訪問介面,並用 單例模式 實現連接池管理,構建靈活、高性能的數據訪問層 (DAL)。
定義核心操作契約,屏蔽底層差異:
public abstract class DBOperator {
public abstract IDbConnection Connection { get; } // 底層連接對象
public abstract void Open(); // 打開連接
public abstract void Close(); // 關閉連接
public abstract void BeginTrans(); // 開啟事務
public abstract void CommitTrans(); // 提交事務
public abstract void RollbackTrans(); // 回滾事務
public abstract void ExecuteSql(string sql, string[] parameters, object[] values); // 執行非查詢
public abstract DataSet ExecuteSqlForDataSet(string query); // 執行查詢返回 DataSet
}
為何用 abstract class 而非 interface? 後文單例連接池需在基類中提供靜態工廠方法 Instance,介面無法包含實現代碼。
internal class SqlDBOperator : DBOperator {
private SqlConnection _conn;
private SqlTransaction _trans;
private bool _inTransaction;
public SqlDBOperator(string connStr) => _conn = new SqlConnection(connStr);
public override IDbConnection Connection => _conn;
public override void Open() {
if (_conn.State != ConnectionState.Open) _conn.Open();
}
public override void Close() {
if (_conn.State == ConnectionState.Open) _conn.Close();
}
public override void BeginTrans() {
_trans = _conn.BeginTransaction();
_inTransaction = true;
}
public override void CommitTrans() { _trans.Commit(); _inTransaction = false; }
public override void RollbackTrans() { _trans.Rollback(); _inTransaction = false; }
public override void ExecuteSql(string sql, string[] parameters, object[] values) {
using (var cmd = _conn.CreateCommand()) {
cmd.CommandText = sql;
if (_inTransaction) cmd.Transaction = _trans;
if (parameters != null) {
if (parameters.Length != values.Length) throw new ArgumentException("參數值不匹配");
for (int i = 0; i < parameters.Length; i++)
cmd.Parameters.AddWithValue(parameters[i], values[i]);
}
cmd.ExecuteNonQuery();
}
}
public override DataSet ExecuteSqlForDataSet(string query) {
using (var cmd = _conn.CreateCommand()) {
cmd.CommandText = query;
if (_inTransaction) cmd.Transaction = _trans;
var adapter = new SqlDataAdapter((SqlCommand)cmd);
var ds = new DataSet();
adapter.Fill(ds);
return ds;
}
}
}
internal class OleDBOperator : DBOperator {
// ... 結構同 SqlDBOperator,替換 SqlXxx 為 OleDbXxx ...
public override void ExecuteSql(string sql, string[] parameters, object[] values) {
using (var cmd = _conn.CreateCommand()) {
cmd.CommandText = ConvertOleDbParameters(sql); // @param → ?
// ... 其餘邏輯相同 ...
}
}
private string ConvertOleDbParameters(string sql) {
// 簡單替換邏輯,實際建議用正則表達式處理
return sql.Replace("@", "?");
}
}
public static class DBOperatorFactory {
public static DBOperator GetDBOperator(string connectionString) {
// 簡單判斷邏輯:OleDb 連接串通常包含 "Provider="
if (connectionString.IndexOf("provider=", StringComparison.OrdinalIgnoreCase) < 0)
return new SqlDBOperator(connectionString);
else
return new OleDBOperator(connectionString);
}
}
// 配置文件中切換連接串,代碼無需修改
string connStr = ConfigurationManager.ConnectionStrings["DefaultDB"].ConnectionString;
using (var db = DBOperatorFactory.GetDBOperator(connStr)) {
db.Open();
var ds = db.ExecuteSqlForDataSet("SELECT * FROM Users WHERE Role=@Role",
new[] {"@Role"}, new object[] {"Admin"});
// ...
db.Close();
}
// 事務模式
using (var db = DBOperatorFactory.GetDBOperator(connStr)) {
db.Open();
db.BeginTrans();
try {
db.ExecuteSql("INSERT ...", ...);
db.ExecuteSql("UPDATE ...", ...);
db.CommitTrans();
} catch {
db.RollbackTrans();
throw;
} finally {
db.Close();
}
}
public abstract class DBOperator {
// ... 抽象成員 ...
public static DBOperator Instance(string connectionString) {
if (connectionString.IndexOf("provider=", StringComparison.OrdinalIgnoreCase) < 0)
return new SqlDBOperator(connectionString);
else
return new OleDBOperator(connectionString);
}
}
// 調用簡化為:
var db = DBOperator.Instance(connStr);
雖然 ADO.NET 內置連接池,但自管池可實現:精確控制連接數上限、連接生命週期監控、跨提供程序統一池化策略。
public abstract class DBOperator {
// 靜態池字段
private static DBOperator[] _pool;
private static int _currentIndex = -1;
private static int _maxPoolSize = 10; // 可從配置讀取
static DBOperator() {
// 從配置初始化池大小
string cfg = ConfigurationManager.AppSettings["PoolCount"];
if (int.TryParse(cfg, out int size) && size > 0) _maxPoolSize = size;
_pool = new DBOperator[_maxPoolSize];
}
// 替換 Instance 方法
public static DBOperator Instance(string connectionString) {
if (_maxPoolSize < 1) return CreateNewOperator(connectionString); // 無池模式
// 循環輪詢策略 (可替換為更高級算法: LRU, 最少連接等)
_currentIndex = (_currentIndex + 1) % _maxPoolSize;
if (_pool[_currentIndex] == null)
_pool[_currentIndex] = CreateNewOperator(connectionString);
// 可選:驗證連接有效性,失效則重建
if (_pool[_currentIndex].Connection.State == ConnectionState.Broken)
_pool[_currentIndex] = CreateNewOperator(connectionString);
return _pool[_currentIndex];
}
private static DBOperator CreateNewOperator(string connStr) {
if (connStr.IndexOf("provider=", StringComparison.OrdinalIgnoreCase) < 0)
return new SqlDBOperator(connStr);
return new OleDBOperator(connStr);
}
// ... 抽象成員保持不變 ...
}
SELECT 1 或 Connection.Ping (MySQL) 檢測存活。Dictionary 多池隔離。| 維度 | 傳統硬編碼 | 本文方案 |
|---|---|---|
| 數據庫遷移 | 修改所有 DAL 代碼 | 僅修改連接串配置 |
| 新增數據庫 | 大量重構 | 新增一個 Operator 類 + 更新工廠判斷邏輯 |
| 連接管理 | 依賴 ADO.NET 黑盒池 | 可視、可控、可擴展的自定義池 |
| 事務處理 | 分散在各業務代碼 | 統一 Begin/Commit/Rollback 介面 |
| 參數化查詢 | 手動拼接 SQL 風險高 | 統一 Parameters 集合防注入 |
「工廠解耦創建,單例管控資源」是構建企業級 DAL 的經典組合拳。本方案在 Websharp 中間件生產環境驗證多年,穩定支撐 SQL Server/Oracle/PostgreSQL 混合部署場景。後續可引入 Dapper/EF Core 作為 Micro-ORM 增強映射能力,或集成 Polly 實現重試/熔斷韌性模式,演進為全功能數據訪問框架。