C#中转义的插值字符串



我发现在C#中创建准备好的语句是不必要的麻烦——通常情况下,您所做的是这样的:

public T GetData<T>(string userInput)
{
string selectSomething = "SELECT * FROM someTable where someCol = @userInput";
using (IDbCommand command = new SqlCommand(selectSomething))
{
IDbDataParameter parameter = new SqlParameter("@userInput", SqlDbType.NVarChar);
parameter.Value = userInput;
command.Parameters.Add(parameter);

IDataReader reader = command.ExecuteReader();
reader.Read();
}
...
}

但现在有了插值字符串,就可以像这样简单了:

public T GetData<T>(string userInput)
{
string selectSomething = $"SELECT * FROM someTable where someCol = {userInput}";

using (IDbCommand command = new SqlCommand(selectSomething))
{
IDataReader reader = command.ExecuteReader();
reader.Read();
}
}

还有一些其他的样板代码,但仍有改进。有没有一种方法可以让插值字符串感到舒适,但仍然可以用这样的东西来保持准备好的语句的安全性:

string selectSomething = $"SELECT * FROM someTable where someCol = {userInput.PreventSQLInjections()}";

如果您不想使用具有FromSqlInterpolated方法的EF或任何其他有助于您处理数据访问的ORM,您可以利用编译器使用FormattableString类型来处理字符串插值的事实来编写类似这样的辅助方法(不完全起作用,但您应该知道(来删除样板代码:

public static class SqlCommandEx
{
// maps CLR type to SqlDbType
private static Dictionary<Type, SqlDbType> typeMap;
static SqlCommandEx()
{
typeMap = new Dictionary<Type, SqlDbType>();
typeMap[typeof(string)] = SqlDbType.NVarChar;
//... all other type maps
}
public static SqlCommand FromInterpolatedString(FormattableString sql)
{
var cmdText = sql.Format;
int count = 0;
var @params = new IDbDataParameter[sql.ArgumentCount];
foreach (var argument in sql.GetArguments())
{
var paramName = $"@param_{count}";
cmdText = cmdText.Replace($"{{{count}}}", paramName);
IDbDataParameter parameter = new SqlParameter(paramName, typeMap[argument.GetType()]);
parameter.Value = argument;
@params[count] = parameter;
count++;
}
var sqlCommand = new SqlCommand(cmdText);
sqlCommand.Parameters.AddRange(@params);
return sqlCommand;
}
}

用途:

using (IDbCommand command = SqlCommandEx.FromInterpolatedString($"Select * from table where id = {val}"))
{
...
}

但这几乎是你自己写ORM,而你通常不应该这样做

准备好的语句/参数化查询不仅仅是清除或转义输入。使用参数化查询时,参数数据将作为独立值从SQL语句中发送。参数数据被never直接替换到SQL中,因此注入受到了完美的保护,而转义/清除输入永远不会。

换言之,不要指望";固定";SQL参数

此外,这真的没有那么多额外的工作。这个问题显示了添加参数的困难。你可以这样简化代码:

public T GetData<T>(string userInput)
{
string selectSomething = "SELECT * FROM someTable where someCol = @userInput";
using (IDbCommand command = new SqlCommand(selectSomething))
{
command.Parameters.Add("@userInput", SqlDbType.NVarChar).Value = userInput;
IDataReader reader = command.ExecuteReader();
reader.Read();
}
...
}

这使得参数的额外工作减少到每个参数一行代码。

如果你对C#类型和SQL类型之间的映射有很高的信心,你可以像这样进一步简化:

command.Parameters.AddWithValue("@userInput", userInput);

只要小心这个快捷方式:如果ADO。Net猜测SQL数据类型错误,它可能会破坏索引并强制进行每行类型的转换,这确实会降低性能。

最新更新