.NET Framework 2.0 中,Microsoft 在 System.Data.Common 名稱空間下定義了一組類用來讓程序員編寫適用於不同數據庫的數據訪問代碼,而且還在 Enterprise Library 中提供了一個標准的示范。但是這裡面有很大的一個問題就是當程序員使用帶參sql的方式來訪問數據庫時,一切都變得毫無意義了。因為 SQL Server 支持以 @ 為前綴的名稱參數、Oracle 則支持以 : 為前綴的名稱參數,而當使用 OleDb 訪問 Access 時就變成了使用 ? 作為占位符的位置參數語法。在編寫程序時,必須針對不同的 ADO.NET Provider 編寫 sql 語句。
本文將介紹一種方法,可以使得 SQL Server、Oracle 也支持以 ? 為占位符的位置參數語法。要想實現這個效果,就需要將 sql 語句中的 ? 占位符替換成相應的名稱參數。下面以 SQL Server 數據庫為例,假如有一條 sql 語句:
select count(0) from sys_user where user_code=? and password=?
如果能夠將其轉變為:
select count(0) from sys_user where user_code=@p1 and password=@p2
就可以使 SQL Server 也支持以 ? 為占位符的位置參數了。
在做這個變換時,我使用了一個正則表達式。通過這個正則表達式,可以將 sql 語句中的所有 ? 占位符找到,並將其替換為以 @ 為前綴的位置參數,代碼如下:
private string PreparePlaceHolder(string srcSql) { string _sqlTokenPattern = "[\\s]+|(?<string>'([^']|'')*')|(?<comment>(/\\*([^\\*]|\\*[^/])*\\*/)|(--.*))|(?<parametermarker>\\?)|(?<query>select)|(?<identifier>([\\p{Lo}\\p{Lu}\\p{Ll}\\p{Lm}\\p{Nd}\\uff3f_#$]+)|(\"([^\"]|\"\")*\"))|(?<other>.)"; Regex sqlTokenParser = new Regex(_sqlTokenPattern, RegexOptions.ExplicitCapture); List<Group> groups = new List<Group>(); bool flag = false; for (Match match = sqlTokenParser.Match(srcSql); Match.Empty != match; match = match.NextMatch()) { if (!match.Groups["comment"].Success) { if ((match.Groups["comment"].Success || match.Groups["string"].Success) || match.Groups["other"].Success) { flag = true; } else if (match.Groups["query"].Success) { if (!flag) { // 走到這裡,表示這是一條 select 語句。 } } else if (match.Groups["parametermarker"].Success) { // 走到這裡,表示發現了一個 ? 占位符。 groups.Add(match.Groups["parametermarker"]); } } } StringBuilder desSql = new StringBuilder(srcSql); for (int i = groups.Count - 1; i >= 0; i--) { Group group = groups[i]; desSql.Remove(group.Index, group.Length); desSql.Insert(group.Index, ParameterToken + "p" + (i + 1)); } return desSql.ToString(); }
利用上述方法,可以編寫出更加通用的 SQLHelper 類。程序員可以在任何時候使用位置參數語法來執行 sql 語句,而不必考慮對應的 ADO.NET Provider 是否支持位置參數。