首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 資料庫存取元件:支援多資料庫提供者的通用 DataAccess 類別完整實作

     編輯:關於C#

前言

在企業級應用開發中,常面臨需支援多種資料庫(SQL Server、Oracle、MySQL、PostgreSQL、Access 等)的需求。硬編碼特定資料庫的連線與指令類別(如 SqlConnection、OracleCommand)會導致程式碼耦合度高、難以維護。.NET Framework 提供了 ADO.NET Provider Factory Model(DbProviderFactory),讓我們能撰寫資料庫無關的通用資料存取程式碼。本文提供一個完整、可直接用於生產環境的 DataAccess 基底類別,支援透過 app.config/web.config 切換資料庫提供者,並封裝常用的 ExecuteCommand、ExecuteScalar、ExecuteReader、GetDataSet、GetDataTable、GetDataView 等方法,包含參數化查詢、交易支援與連線池管理。

核心架構設計

  1. 抽象工廠模式:透過 DbProviderFactory 動態建立 DbConnection、DbCommand、DbDataAdapter、DbParameter。
  2. 設定驅動:連線字串與提供者名稱皆從 ConfigurationManager.ConnectionStrings 讀取,支援多連線字串切換。
  3. 資源管理:所有方法採用 try-catch-finally 確保連線正確關閉,支援 using 模式(可擴充實作 IDisposable)。
  4. 交易支援:提供 ExecuteCommand(ArrayList sqlList) 批次執行 SQL 並自動交易控制(Commit/Rollback)。

完整 DataAccess 類別程式碼

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

namespace DBUtility
{
    /// 
    /// 通用資料庫存取基底類別,支援多資料庫提供者 (SQL Server, Oracle, OleDb, MySQL, PostgreSQL 等)
    /// 
    public class DataAccess : IDisposable
    {
        private string _configString = "ConnectionString"; // 預設連線字串名稱

        public DataAccess() { }

        public DataAccess(string configString)
        {
            ConfigString = configString;
        }

        /// 
        /// 組態檔中連線字串的名稱 (對應 app.config/web.config 的 connectionStrings 區段 name 屬性)
        /// 
        public string ConfigString
        {
            get => _configString;
            set => _configString = value ?? "ConnectionString";
        }

        #region 取得提供者資訊

        /// 
        /// 取得資料提供者名稱 (如 System.Data.SqlClient)
        /// 
        public string GetProviderName(string configString = null)
        {
            var settings = GetConnectionStringSettings(configString);
            return settings?.ProviderName ?? "System.Data.SqlClient";
        }

        private ConnectionStringSettings GetConnectionStringSettings(string configString = null)
        {
            var collection = ConfigurationManager.ConnectionStrings;
            if (collection == null || collection.Count == 0)
                throw new InvalidOperationException("組態檔中找不到任何連線字串 (connectionStrings 區段為空)。");

            string name = string.IsNullOrEmpty(configString) ? ConfigString : configString;
            var settings = collection[name];
            if (settings == null)
                throw new InvalidOperationException($"組態檔中找不到名稱為 '{name}' 的連線字串。");
            return settings;
        }

        #endregion

        #region 取得連線字串

        public string GetConnectionString(string configString = null)
        {
            return GetConnectionStringSettings(configString).ConnectionString;
        }

        #endregion

        #region DbProviderFactory 管理

        private DbProviderFactory _factoryCache;
        private string _cachedProviderName;

        /// 
        /// 取得或建立 DbProviderFactory (含快取機制)
        /// 
        protected DbProviderFactory GetDbProviderFactory(string configString = null)
        {
            string providerName = GetProviderName(configString);
            if (_factoryCache != null && _cachedProviderName == providerName)
                return _factoryCache;

            _factoryCache = DbProviderFactories.GetFactory(providerName);
            _cachedProviderName = providerName;
            return _factoryCache;
        }

        #endregion

        #region 建立連線、命令、適配器、參數

        public DbConnection CreateConnection(string configString = null)
        {
            var factory = GetDbProviderFactory(configString);
            var conn = factory.CreateConnection();
            conn.ConnectionString = GetConnectionString(configString);
            return conn;
        }

        public DbCommand CreateCommand(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            var factory = GetDbProviderFactory(configString);
            var cmd = factory.CreateCommand();
            cmd.Connection = CreateConnection(configString);
            cmd.CommandText = sql;
            cmd.CommandType = cmdType;
            if (parameters != null && parameters.Length > 0)
                cmd.Parameters.AddRange(parameters);
            return cmd;
        }

        public DbDataAdapter CreateAdapter(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            var factory = GetDbProviderFactory(configString);
            var cmd = factory.CreateCommand();
            cmd.Connection = CreateConnection(configString);
            cmd.CommandText = sql;
            cmd.CommandType = cmdType;
            if (parameters != null && parameters.Length > 0)
                cmd.Parameters.AddRange(parameters);
            var adapter = factory.CreateDataAdapter();
            adapter.SelectCommand = cmd;
            return adapter;
        }

        public DbParameter CreateParameter(string parameterName, object value, string configString = null)
        {
            var factory = GetDbProviderFactory(configString);
            var param = factory.CreateParameter();
            param.ParameterName = parameterName;
            param.Value = value ?? DBNull.Value;
            return param;
        }

        #endregion

        #region 執行非查詢 (INSERT/UPDATE/DELETE/DDL)

        public int ExecuteCommand(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            using (var cmd = CreateCommand(sql, cmdType, parameters, configString))
            {
                try
                {
                    cmd.Connection.Open();
                    return cmd.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    throw new DataAccessException($"執行命令失敗: {ex.Message}", ex);
                }
            }
        }

        /// 
        /// 批次執行多條 SQL (交易模式),任一失敗全數 Rollback
        /// 
        public bool ExecuteCommandBatch(IList sqlList, string configString = null)
        {
            using (var conn = CreateConnection(configString))
            {
                conn.Open();
                using (var tran = conn.BeginTransaction())
                {
                    try
                    {
                        var factory = GetDbProviderFactory(configString);
                        foreach (string sql in sqlList)
                        {
                            using (var cmd = factory.CreateCommand())
                            {
                                cmd.Connection = conn;
                                cmd.Transaction = tran;
                                cmd.CommandText = sql;
                                cmd.ExecuteNonQuery();
                            }
                        }
                        tran.Commit();
                        return true;
                    }
                    catch (Exception ex)
                    {
                        tran.Rollback();
                        throw new DataAccessException($"批次執行失敗,已回滾: {ex.Message}", ex);
                    }
                }
            }
        }

        #endregion

        #region 執行純量查詢 (單一值)

        public object ExecuteScalar(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            using (var cmd = CreateCommand(sql, cmdType, parameters, configString))
            {
                try
                {
                    cmd.Connection.Open();
                    return cmd.ExecuteScalar();
                }
                catch (Exception ex)
                {
                    throw new DataAccessException($"執行純量查詢失敗: {ex.Message}", ex);
                }
            }
        }

        #endregion

        #region 執行查詢回傳 DataReader (唯讀、順向、資料流)

        public DbDataReader ExecuteReader(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            var cmd = CreateCommand(sql, cmdType, parameters, configString);
            try
            {
                cmd.Connection.Open();
                // CommandBehavior.CloseConnection: DataReader 關閉時自動關閉連線
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            catch
            {
                cmd.Connection.Close();
                cmd.Dispose();
                throw;
            }
        }

        #endregion

        #region 執行查詢回傳 DataSet / DataTable / DataView (離線資料集)

        public DataSet GetDataSet(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            using (var adapter = CreateAdapter(sql, cmdType, parameters, configString))
            {
                var ds = new DataSet();
                adapter.Fill(ds);
                return ds;
            }
        }

        public DataSet GetDataSetPaged(string sql, int startIndex, int recordCount, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            using (var adapter = CreateAdapter(sql, cmdType, parameters, configString))
            {
                var ds = new DataSet();
                adapter.Fill(ds, startIndex, recordCount, "Table");
                return ds;
            }
        }

        public DataTable GetDataTable(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            return GetDataSet(sql, cmdType, parameters, configString).Tables[0];
        }

        public DataTable GetDataTablePaged(string sql, int startIndex, int recordCount, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            return GetDataSetPaged(sql, startIndex, recordCount, cmdType, parameters, configString).Tables[0];
        }

        public DataTable GetDataTableWithEmptyRows(string sql, int minRows, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            var dt = GetDataTable(sql, cmdType, parameters, configString);
            int need = minRows - dt.Rows.Count;
            for (int i = 0; i < need; i++)
                dt.Rows.Add(dt.NewRow());
            return dt;
        }

        public DataView GetDataView(string sql, CommandType cmdType = CommandType.Text, DbParameter[] parameters = null, string configString = null)
        {
            return GetDataSet(sql, cmdType, parameters, configString).Tables[0].DefaultView;
        }

        #endregion

        #region IDisposable 實作 (可選,若衍生類別持有非受控資源)

        private bool _disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    _factoryCache = null;
                }
                _disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }

    /// 
    /// 資料存取層自訂例外
    /// 
    public class DataAccessException : Exception
    {
        public DataAccessException(string message, Exception innerException) : base(message, innerException) { }
        public DataAccessException(string message) : base(message) { }
    }
}

組態檔設定範例 (app.config / web.config)

<configuration>
  <connectionStrings>
    <!-- SQL Server -->
    <add name="SqlServerConn" 
         connectionString="Server=.;Database=MyDB;User Id=sa;Password=123456;" 
         providerName="System.Data.SqlClient" />
    
    <!-- Oracle (需安裝 Oracle.ManagedDataAccess) -->
    <add name="OracleConn" 
         connectionString="Data Source=ORCL;User Id=scott;Password=tiger;" 
         providerName="Oracle.ManagedDataAccess.Client" />
    
    <!-- MySQL (需安裝 MySql.Data) -->
    <add name="MySqlConn" 
         connectionString="Server=localhost;Database=mydb;Uid=root;Pwd=123456;" 
         providerName="MySql.Data.MySqlClient" />
    
    <!-- PostgreSQL (需安裝 Npgsql) -->
    <add name="PostgreConn" 
         connectionString="Host=localhost;Database=mydb;Username=postgres;Password=123456;" 
         providerName="Npgsql" />
    
    <!-- Access (OleDb) -->
    <add name="AccessConn" 
         connectionString="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\db.accdb;" 
         providerName="System.Data.OleDb" />
  </connectionStrings>
</configuration>

實際使用範例

using System;
using System.Data;
using System.Data.Common;
using DBUtility;

class Program
{
    static void Main()
    {
        // 使用預設連線字串 (name="ConnectionString")
        var db = new DataAccess("SqlServerConn");

        // 1. 參數化查詢 - 防止 SQL Injection
        string sql = "SELECT * FROM Users WHERE Age > @Age AND City = @City";
        var parameters = new DbParameter[]
        {
            db.CreateParameter("@Age", 18),
            db.CreateParameter("@City", "Taipei")
        };

        DataTable dt = db.GetDataTable(sql, CommandType.Text, parameters);
        Console.WriteLine($"查詢到 {dt.Rows.Count} 筆資料");
        foreach (DataRow row in dt.Rows)
            Console.WriteLine($"{row["Id"]}\t{row["Name"]}\t{row["Age"]}\t{row["City"]}");

        // 2. 執行非查詢 (新增/修改/刪除)
        int affected = db.ExecuteCommand(
            "INSERT INTO Users (Name, Age, City) VALUES (@Name, @Age, @City)",
            CommandType.Text,
            new DbParameter[]
            {
                db.CreateParameter("@Name", "John"),
                db.CreateParameter("@Age", 25),
                db.CreateParameter("@City", "Taichung")
            });
        Console.WriteLine($"異動 {affected} 筆資料");

        // 3. 純量查詢
        object countObj = db.ExecuteScalar("SELECT COUNT(*) FROM Users");
        Console.WriteLine($"總筆數:{countObj}");

        // 4. 批次交易執行
        var sqlBatch = new System.Collections.ArrayList
        {
            "UPDATE Users SET Age = Age + 1 WHERE City = 'Taipei'",
            "UPDATE Users SET LastLogin = GETDATE() WHERE City = 'Taipei'"
        };
        bool success = db.ExecuteCommandBatch(sqlBatch);
        Console.WriteLine($"批次交易 {(success ? "成功" : "失敗")}");

        // 5. DataReader 串流讀取 (大量資料、唯讀、高效能)
        using (DbDataReader reader = db.ExecuteReader("SELECT Name, Email FROM Users WHERE IsActive = 1"))
        {
            while (reader.Read())
                Console.WriteLine($"{reader["Name"]} - {reader["Email"]}");
        }

        db.Dispose();
        Console.ReadKey();
    }
}

進階擴充建議

功能實作方向
非同步支援新增 ExecuteCommandAsync、GetDataTableAsync 等方法,使用 await cmd.ExecuteNonQueryAsync() 等
連線重試原則整合 Polly 或 Microsoft.Data.SqlClient 內建重試邏輯
SQL 記錄與效能監控在 CreateCommand 後攔截,記錄 SQL、參數、執行時間
對應 Entity/DTO搭配 Dapper 或自寫 MapToList<T>(DbDataReader) 實現物件映射
分頁查詢標準化針對不同資料庫方言 (OFFSET/FETCH, ROWNUM, LIMIT) 產生分頁 SQL

總結

本文提供的 DataAccess 類別完整實作了 ADO.NET Provider Factory 模式,支援多資料庫無縫切換、參數化查詢、交易批次、分頁查詢、DataReader 串流讀取等企業級常用功能。程式碼採用 using 區塊確保資源正確釋放,並提供自訂例外 DataAccessException 統一錯誤處理。開發者只需在組態檔調整連線字串與提供者名稱,即可在不修改業務邏輯的前提下切換資料庫,極大提升系統的可維護性與擴充性。

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