.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 是否支持位置参数。
新闻热点
疑难解答