前言
防SQL注入,常用的方案是使用Dapper执行SQL的参数化查询。例如:
using (IDbConnection conn = CreateConnection())
{string sqlCommandText = @"SELECT * FROM USERS WHERE ID=@ID";Users user = conn.Query<Users>(sqlCommandText, new { ID = 2 }).FirstOrDefault();Console.WriteLine(user.Name);
}
但是,不同数据库支持不同的sql参数格式,例如,ORACLE必须使用:ID
,否则上述代码会报错:
Pseudo-Positional Parameters
查看Dapper的源代码[1],发现有这样一段:
cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match =>
{string key = match.Groups[1].Value;if (!consumed.Add(key)){throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once");}else if (parameters.TryGetValue(key, out IDbDataParameter param)){if (firstMatch){firstMatch = false;cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully}// if found, return the anonymous token "?"if (Settings.UseIncrementalPseudoPositionalParameterNames){param.ParameterName = (++index).ToString();}cmd.Parameters.Add(param);parameters.Remove(key);consumed.Add(key);return "?";}else{// otherwise, leave alone for simple debuggingreturn match.Value;}
});
通过查看Dapper教程[2],原来,这是实现被称为Pseudo-Positional Parameters
(伪位置参数)的代码,作用是为了不支持命名参数的数据库提供者能够使用参数化SQL,例如OleDB:
//代码
var docs = conn.Query<Document>(@"select * from Documentswhere Region = ?region?and OwnerId in ?users?", new { region, users }).AsList();//SQL
select * from Documentswhere Region = ?and OwnerId in (?,?,?)
自定义Pseudo-Positional Parameters
依葫芦画瓢,我们可以实现自己的Pseudo-Positional Parameters
,以便支持更多的数据提供者:
private static readonly Regex pseudoRegex = new Regex(@"\$([\p{L}_][\p{L}\p{N}_]*)\$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);public static string ReplacePseudoParameter(IDbConnection conn, string cmdText)
{return pseudoRegex.Replace(cmdText, match => {var key = match.Groups[1].Value;if (conn is OleDbConnection){return "?" + key + "?";}return (conn is OracleConnection ? ":" : "@") + key;});
}
使用方式是在参数名称两边加上$
:
string sqlCommandText = @"SELECT * FROM USERS WHERE ID=$ID$";Users user = conn.Query<Users>(ReplacePseudoParameter(conn, sqlCommandText), new { ID = 2 }).FirstOrDefault();
结论
通过实现Pseudo-Positional Parameters
功能,我们让Dapper防sql注入支持了多种数据库。
如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“
参考资料
[1]
源代码: https://github.com/DapperLib/Dapper/blob/main/Dapper/SqlMapper.cs#L1783
[2]Dapper教程: https://riptutorial.com/Dapper/example/13835/pseudo-positional-parameters--for-providers-that-don-t-support-named-parameters-