C# 中的转义内插字符串
Escaped interpolated strings in 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,而您通常不应该这样做。
准备好的 statements/parameterized 查询比仅仅清理或转义输入更进一步。当您使用参数化查询时,参数数据作为 独立值 从 SQL 语句发送。参数数据 永远不会 直接代入 SQL,因此注入得到了完美的保护,escaping/sanitizing 输入永远不会。
换句话说,不要指望“固定”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 数据类型错误,它可能会破坏索引并强制执行每行类型转换,这确实会降低性能。
我发现在 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,而您通常不应该这样做。
准备好的 statements/parameterized 查询比仅仅清理或转义输入更进一步。当您使用参数化查询时,参数数据作为 独立值 从 SQL 语句发送。参数数据 永远不会 直接代入 SQL,因此注入得到了完美的保护,escaping/sanitizing 输入永远不会。
换句话说,不要指望“固定”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 数据类型错误,它可能会破坏索引并强制执行每行类型转换,这确实会降低性能。