許多開發者追求「一套代碼跑遍所有數據庫」,但現實是:不同 RDBMS 的 SQL 方言、優化器特性、鎖機制、並發控制差異巨大。僅使用通用接口(如 IDbConnection、IDbCommand)並編寫通用 SQL,必然導致:
關鍵在於在業務邏輯層與數據庫物理實現之間,建立「領域驅動的數據訪問契約」,而非單純的 ADO.NET 接口封裝。
封裝連接、命令、適配器的創建,屏蔽具體類型:
public enum DatabaseType { SqlServer, Oracle, PostgreSQL, MySQL }
public static class DbProviderFactory
{
public static IDbConnection CreateConnection(string connStr, DatabaseType type)
{
return type switch
{
DatabaseType.SqlServer => new SqlConnection(connStr),
DatabaseType.Oracle => new OracleConnection(connStr),
DatabaseType.PostgreSQL => new NpgsqlConnection(connStr),
DatabaseType.MySQL => new MySqlConnection(connStr),
_ => throw new ArgumentException("Unsupported database type")
};
}
public static IDbCommand CreateCommand(string cmdText, IDbConnection conn, DatabaseType type)
{
var cmd = conn.CreateCommand();
cmd.CommandText = cmdText;
return cmd; // 具體參數創建由上層調用 DbParameter 集合統一處理
}
public static DbDataAdapter CreateAdapter(IDbCommand cmd, DatabaseType type)
{
return type switch
{
DatabaseType.SqlServer => new SqlDataAdapter((SqlCommand)cmd),
DatabaseType.Oracle => new OracleDataAdapter((OracleCommand)cmd),
DatabaseType.PostgreSQL => new NpgsqlDataAdapter((NpgsqlCommand)cmd),
DatabaseType.MySQL => new MySqlDataAdapter((MySqlCommand)cmd),
_ => throw new ArgumentException("Unsupported database type")
};
}
}
定義領域操作,而非 SQL 操作:
public interface ICustomerRepository
{
Customer GetById(int id);
IEnumerable GetByCountry(string countryCode);
int Insert(Customer customer);
bool Update(Customer customer);
void Delete(int id);
}
每個數據庫一個實現項目,充分利用該數據庫特性:
// SQL Server 實現 - 利用存儲過程、表值參數、MERGE 語句
public class SqlCustomerRepository : ICustomerRepository
{
private readonly string _connStr;
public SqlCustomerRepository(string connStr) => _connStr = connStr;
public Customer GetById(int id)
{
using var conn = new SqlConnection(_connStr);
using var cmd = new SqlCommand("usp_GetCustomerById", conn) { CommandType = CommandType.StoredProcedure };
cmd.Parameters.AddWithValue("@Id", id);
conn.Open();
using var reader = cmd.ExecuteReader();
return reader.Read() ? MapCustomer(reader) : null;
}
public int Insert(Customer customer)
{
using var conn = new SqlConnection(_connStr);
using var cmd = new SqlCommand(@"
INSERT INTO Customers (Name, CountryCode, CreatedAt)
OUTPUT INSERTED.Id
VALUES (@Name, @CountryCode, SYSUTCDATETIME())", conn);
cmd.Parameters.AddWithValue("@Name", customer.Name);
cmd.Parameters.AddWithValue("@CountryCode", customer.CountryCode);
conn.Open();
return (int)cmd.ExecuteScalar();
}
// ... 其他方法利用 SQL Server 特性優化
}
// Oracle 實現 - 利用序列、PL/SQL 包、游標變量
public class OracleCustomerRepository : ICustomerRepository { /* ... */ }
// 配置文件
{
"DatabaseType": "SqlServer",
"ConnectionString": "Server=...;Database=...;",
"RepositoryAssembly": "MyApp.Data.SqlServer",
"RepositoryType": "MyApp.Data.SqlServer.SqlCustomerRepository"
}
// 運行時工廠
public static class RepositoryFactory
{
public static ICustomerRepository CreateCustomerRepository(IConfiguration config)
{
var asmName = config["RepositoryAssembly"];
var typeName = config["RepositoryType"];
var connStr = config.GetConnectionString("Default");
var asm = Assembly.Load(asmName);
var type = asm.GetType(typeName);
return (ICustomerRepository)Activator.CreateInstance(type, connStr);
}
}
public class CustomerService
{
private readonly ICustomerRepository _repo;
public CustomerService(ICustomerRepository repo) => _repo = repo;
public CustomerDto GetCustomer(int id) => _repo.GetById(id).ToDto();
public int CreateCustomer(CreateCustomerDto dto) => _repo.Insert(dto.ToEntity());
}
| 原則 | 說明 |
|---|---|
| 接口定義領域操作,不暴露 SQL | 業務層依賴抽象,而非具體數據庫技術 |
| 每個數據庫一個實現項目 | 充分利用該數據庫的優化特性(存儲過程、批處理、特有語法) |
| 配置驅動運行時綁定 | 切換數據庫只需修改配置與部署對應實現程序集 |
| 連接池、參數化、異步贏在細節 | 無論哪種實現,都必須遵循 ADO.NET 最佳實踐 |
| 測試驅動實現 | 用同一套集成測試驗證不同實現的一致性 |
對於新項目,建議考慮:
但理解上述分層思想,有助於在 ORM 無法滿足性能或特殊需求時,正確地編寫底層數據訪問代碼。