首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

運用 Factory 與 Singleton 模式建構通用資料庫存取元件

     編輯:VC++

前言:為什麼需要再封裝一次 ADO.NET?

.NET Framework 針對 SQL Server (System.Data.SqlClient)、OLE DB (System.Data.OleDb) 與 Oracle 提供不同的提供者類別。若業務邏輯直接耦合具體類別,未來切換資料庫將牽一髮動全身。本文示範如何以 Factory Method 統一建立介面、以 Singleton 管理連線池,打造「資料庫無關」的資料存取層(DAL)。

一、定義統一介面:DBOperator 抽象類別

使用 abstract class 而非 interface,是為了後續在基類實作 Instance 靜態工廠方法與連線池邏輯:

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 queryString);

    // --- 靜態工廠方法(自管理 Factory) ---
    public static DBOperator Instance(string connectionString)
    {
        if (connectionString.IndexOf("provider=", StringComparison.OrdinalIgnoreCase) < 0)
            return new SqlDBOperator(connectionString);
        else
            return new OleDBOperator(connectionString);
    }
}

二、具體實作:SqlDBOperator 與 OleDBOperator

SQL Server 版

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 queryString)
    {
        using (var cmd = _conn.CreateCommand())
        using (var adapter = new SqlDataAdapter((SqlCommand)cmd))
        {
            cmd.CommandText = queryString;
            if (_inTransaction) cmd.Transaction = _trans;
            var ds = new DataSet();
            adapter.Fill(ds);
            return ds;
        }
    }
}

OLE DB 版(關鍵差異:參數前綴 @ → ?)

internal class OleDBOperator : DBOperator
{
    private OleDbConnection _conn;
    private OleDbTransaction _trans;
    private bool _inTransaction;

    public OleDBOperator(string connStr) => _conn = new OleDbConnection(connStr);

    public override IDbConnection Connection => _conn;
    // Open/Close/Transaction 方法同 SqlDBOperator,略

    public override void ExecuteSql(string sql, string[] parameters, object[] values)
    {
        using (var cmd = _conn.CreateCommand())
        {
            cmd.CommandText = ConvertParameters(sql);  // @param → ?
            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();
        }
    }

    private string ConvertParameters(string sql)
    {
        // 簡易轉換:@Name → ?  (實務建議用 Regex 完整解析)
        return sql.Replace("@", "?");
    }
    // ExecuteSqlForDataSet 同理,略
}

三、外部工廠類別:DBOperatorFactory(可選)

若偏好顯式工廠類別而非基類靜態方法:

public static class DBOperatorFactory
{
    public static DBOperator Create(string connectionString)
    {
        return connectionString.Contains("provider=", StringComparison.OrdinalIgnoreCase)
            ? new OleDBOperator(connectionString)
            : new SqlDBOperator(connectionString);
    }
}

用戶端呼叫:

var db = DBOperatorFactory.Create(connStr);
db.Open();
db.ExecuteSql("INSERT INTO ...", new[]{"@p1"}, new object[]{val});
db.Close();

四、Singleton 連線池實作(進階)

在 DBOperator 基類加入靜態陣列與索引,實作簡易 Round-Robin 池:

public abstract class DBOperator
{
    // ... 介面定義 ...

    private static DBOperator[] _pool;
    private static int _poolSize;
    private static int _nextIndex = -1;
    private static readonly object _lock = new object();

    static DBOperator()
    {
        // 從設定檔讀取池大小
        _poolSize = int.Parse(ConfigurationManager.AppSettings["PoolCount"] ?? "0");
        if (_poolSize > 0) _pool = new DBOperator[_poolSize];
    }

    public static DBOperator GetPooledInstance(string connectionString)
    {
        if (_poolSize == 0) return CreateNew(connectionString);

        lock (_lock)
        {
            _nextIndex = (_nextIndex + 1) % _poolSize;
            if (_pool[_nextIndex] == null)
                _pool[_nextIndex] = CreateNew(connectionString);
            return _pool[_nextIndex];
        }
    }

    private static DBOperator CreateNew(string connStr)
    {
        return connStr.Contains("provider=", StringComparison.OrdinalIgnoreCase)
            ? new OleDBOperator(connStr)
            : new SqlDBOperator(connStr);
    }
}

注意: 連線池需搭配 Open/Close 語意調整(Close 不真正關閉實體連線),實務上建議直接使用 ADO.NET 內建 Connection Pooling (Pooling=true; Min Pool Size=5; Max Pool Size=100),僅在極特殊需求下自行實作。

五、交易處理範本

var db = DBOperator.Instance(connStr);
db.Open();
db.BeginTrans();
try
{
    db.ExecuteSql(sql1, params1, values1);
    db.ExecuteSql(sql2, params2, values2);
    db.CommitTrans();
}
catch
{
    db.RollbackTrans();
    throw;
}
finally
{
    db.Close();
}

結語

結合 Factory Method 隔離提供者差異、Singleton 管理連線實例,即可建構出彈性、可維護的通用 DAL。核心原則:業務邏輯只依賴 DBOperator 抽象介面,具體實作細節完全封裝於衍生類別與工廠內部。專案實務中,建議優先使用 ADO.NET 內建連線池,並在必要時才引入自訂池邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved