專案初期選用 MySQL,後期客戶要求改 SQL Server。利用 C# 泛型約束 `where T : IDbConnection, new()`,將資料存取層抽象為泛型類別,配合 `using` 別名指令與條件編譯,**僅修改 < 100 行基礎設施代碼,業務邏輯 SQL 完全不動**,實現資料庫無縫遷移。
public class ConnectionPool where T : IDbConnection, new()
{
private readonly ConcurrentQueue _pool = new ConcurrentQueue();
private readonly string _connectionString;
private readonly int _maxSize;
public ConnectionPool(string connStr, int maxSize = 20)
{
_connectionString = connStr;
_maxSize = maxSize;
}
public T Acquire()
{
if (_pool.TryDequeue(out T conn)) return conn;
conn = new T { ConnectionString = _connectionString };
conn.Open();
return conn;
}
public void Release(T conn)
{
if (conn.State == ConnectionState.Open && _pool.Count < _maxSize)
_pool.Enqueue(conn);
else
conn.Dispose();
}
}
public class DataProvider
where TConn : IDbConnection, new()
where TCmd : IDbCommand, new()
{
private readonly ConnectionPool _pool;
public DataProvider(string connStr) => _pool = new ConnectionPool(connStr);
// 通用查詢
public IEnumerable Query(string sql, Func map, params object[] parameters)
{
using var conn = _pool.Acquire();
using var cmd = CreateCommand(conn, sql, parameters);
using var reader = cmd.ExecuteReader();
while (reader.Read()) yield return map(reader);
}
// 通用執行
public int Execute(string sql, params object[] parameters)
{
using var conn = _pool.Acquire();
using var cmd = CreateCommand(conn, sql, parameters);
return cmd.ExecuteNonQuery();
}
// 純量查詢
public object ExecuteScalar(string sql, params object[] parameters)
{
using var conn = _pool.Acquire();
using var cmd = CreateCommand(conn, sql, parameters);
return cmd.ExecuteScalar();
}
private TCmd CreateCommand(TConn conn, string sql, object[] parameters)
{
var cmd = new TCmd { Connection = conn, CommandText = sql };
for (int i = 0; i < parameters.Length; i++)
{
var p = cmd.CreateParameter();
p.ParameterName = $"@p{i}";
p.Value = parameters[i] ?? DBNull.Value;
cmd.Parameters.Add(p);
}
return cmd;
}
}
#if MSSQLSERVER
using System.Data.SqlClient;
using DataProvider = MyProject.Data.DataProvider;
#elif MYSQL
using MySql.Data.MySqlClient;
using DataProvider = MyProject.Data.DataProvider;
#else
#error "請定義編譯符號 MSSQLSERVER 或 MYSQL"
#endif
// UserRepository.cs - 完全不變
public class UserRepository
{
private readonly DataProvider _dp; // 編譯時解析為具體型別
public UserRepository(DataProvider dp) => _dp = dp;
public User GetById(int id)
{
return _dp.Query("SELECT * FROM Users WHERE Id=@p0", r => new User
{
Id = r.GetInt32(0),
Name = r.GetString(1),
Email = r.GetString(2)
}, id).FirstOrDefault();
}
public void Insert(User u)
{
_dp.Execute("INSERT INTO Users(Name,Email) VALUES(@p0,@p1)", u.Name, u.Email);
}
}
public void ConfigureServices(IServiceCollection services)
{
#if MSSQLSERVER
services.AddScoped(sp =>
new DataProvider(Configuration.GetConnectionString("SqlServer")));
#elif MYSQL
services.AddScoped(sp =>
new DataProvider(Configuration.GetConnectionString("MySQL")));
#endif
services.AddScoped();
}
| 類別 | 工具 | 用途 |
|---|---|---|
| IDE | VS 2022 Community / VS Code + C# Dev Kit | 開發環境 |
| 版控 | Git + GitHub / GitLab | 原始碼管理 |
| 建模 | StarUML / Draw.io | UML/ER 圖 |
| 資料庫 | DBeaver / Azure Data Studio | 通用 GUI 管理 |
| 腳本 | PowerShell 7+ | 自動化部署 |
**泛型 + 條件編譯 + 介面導向** 是 .NET Framework 時代跨資料庫的經典模式。現代 .NET Core/5+ 專案,**首選 Entity Framework Core**,其 Provider 模型(`UseMySQL`/`UseSqlServer`)在編譯期即解決方言差異,支援 LINQ、遷移、快取、追蹤等豐富功能,無需手寫泛型基礎設施。本模式適用於**無法引入 EF Core 的舊專案改造**、**極簡輕量專案**、**教學示範 ADO.NET 抽象原理**。