ADO.NET (特別是 OleDb/Jet/ACE Provider) 在建構動態 SQL 時,有別於 T-SQL 或標準 ANSI SQL 的特殊語法規則。本文系統整理:通配符用法、單引號跳脫、日期常值格式、識別碼分隔符處理,並提供防注入的參數化查詢最佳實務。
% 與 * 的雙重標準| Provider | LIKE 通配符 | 範例 |
|---|---|---|
SQL Server (SqlClient) | % (多字元)、_ (單字元) | WHERE Name LIKE 'A%' |
Access / Excel (OleDb Jet/ACE) | * (多字元)、? (單字元) | WHERE Name LIKE 'A*' |
Oracle (OracleClient/ODP.NET) | % / _ | 標準 SQL |
坑點:同一應用若支援多資料庫,**不可硬編碼通配符**,應在 DAL 層依 Provider 替換。
''
// 錯誤:直接拼接
string sql = $"SELECT * FROM Users WHERE Name = '{userInput}'";
// 若 userInput = "O'Brien" → SQL 語法錯誤
// 正確:字串常值中單引號寫兩個
string safe = userInput.Replace("'", "''");
string sql = $"SELECT * FROM Users WHERE Name = '{safe}'";
// O''Brien → 正確
// 更好:參數化查詢 (完全避免跳脫問題)
using var cmd = new OleDbCommand("SELECT * FROM Users WHERE Name = @Name", conn);
cmd.Parameters.AddWithValue("@Name", userInput);
#yyyy/MM/dd# (Jet/ACE 專用)
// Access / Excel 日期常值必須用 # 包圍,格式建議 ISO (yyyy/MM/dd)
string sql = "SELECT * FROM Orders WHERE OrderDate > #2024/12/25#";
// 參數化查詢自動處理格式
cmd.Parameters.Add("@Date", OleDbType.Date).Value = DateTime.Today;
'2024-12-25' 或 '2024/12/25' (單引號)'2024-12-25' (單引號) 或參數化[] 與跳脫當資料表/欄位名稱包含空格、保留字、特殊字元時:
// 包含空格
SELECT [Order ID], [Customer Name] FROM [Order Details]
// 包含 ] 字元本身:在 ] 前加 \
SELECT [Sale\] Order] FROM Products -- 欄位名實為 "Sale] Order"
// C# 字串中 \ 需再跳脫
string col = "[Sale\\] Order]"; // 實際 SQL: [Sale\] Order]
對照:SQL Server 也支援 [],標準 SQL 使用雙引號 "" (需 SET QUOTED_IDENTIFIER ON),MySQL 使用反引號 ``。
// OleDb 參數順序必須與 SQL 中 ? 出現順序一致 (不支援命名參數)
string sql = "INSERT INTO Logs (Level, Message, CreatedAt) VALUES (?, ?, ?)";
using var cmd = new OleDbCommand(sql, conn);
cmd.Parameters.Add("@Level", OleDbType.VarWChar).Value = level;
cmd.Parameters.Add("@Msg", OleDbType.LongVarWChar).Value = message;
cmd.Parameters.Add("@Time", OleDbType.DBTimeStamp).Value = DateTime.Now;
cmd.ExecuteNonQuery();
// SqlClient 支援命名參數,順序不拘
string sql = "INSERT INTO Logs (Level, Message, CreatedAt) VALUES (@Level, @Msg, @Time)";
using var cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@Level", level); // 隱含類型推斷
// 建議明確指定類型避免推斷錯誤
cmd.Parameters.Add("@Msg", SqlDbType.NVarChar, -1).Value = message;
cmd.Parameters.Add("@Time", SqlDbType.DateTime2).Value = DateTime.Now;
OleDbType 對照表 (常用)| .NET 類型 | OleDbType | SQL Server 對應 |
|---|---|---|
string | VarWChar (Unicode)、VarChar (ANSI) | NVarChar / VarChar |
int | Integer | Int |
long | BigInt | BigInt |
bool | Boolean | Bit |
DateTime | DBTimeStamp | DateTime2 |
byte[] | LongVarBinary | VarBinary |
decimal | Decimal | Decimal |
Guid | Guid | UniqueIdentifier |
public static class SqlBuilder
{
public static string EscapeIdentifier(string ident, char quote = '[')
=> quote + ident.Replace("]", "]\]") + (quote == '[' ? "]" : quote.ToString());
public static string EscapeLiteral(string literal)
=> literal.Replace("'", "''");
public static string FormatDate(DateTime dt, string provider)
=> provider switch
{
"OleDb" => $"#{dt:yyyy/MM/dd}#",
"SqlClient" => $"'{dt:yyyy-MM-dd}'",
_ => $"'{dt:yyyy-MM-dd}'" // ANSI 標準
};
}
Replace("'", "''")、日期用 Provider 專用格式、識別碼用 [] 並跳脫 ]SqlClient 用 %,OleDb 用 *OleDbType/SqlDbType),避免隱含推斷導致計畫快取汙染ADO.NET 的語法陷阱多源於不同 Provider 實作差異。現代開發建議:
IDbCommandBuilder 抽象層,封裝 Provider 差異。