简体   繁体   English

在 C# 中转义内插字符串

[英]Escaped interpolated strings in C#

I find it unnecessarily cumbersome to create prepared statements in C# generally what you do is something like this:我发现在 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();
        }

        ...
    }

But now as there are interpolated strings it could be as easy like this:但是现在由于有内插字符串,它可以像这样简单:

    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();
        }
    }

There's still some other boilerplate code but still an improvement.还有一些其他样板代码,但仍有改进。 Is there maybe a way to get the comfort of interpolated strings but still keep the safety of prepared statements with something like this:有没有办法获得内插字符串的舒适性,但仍然保持准备好的语句的安全性,如下所示:

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

If you don't want to use EF which has FromSqlInterpolated method or any other ORM which will help you to handle your data access you can leverage fact that compiler uses FormattableString type to handle string interpolation to write helper method looking something like this (not fully working, but you should get the idea) to remove the boilerplate code:如果您不想使用具有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;
    }
}

And usage:和用法:

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

But this comes close to writing your own ORM which you usually should not do.但这接近于编写您自己的 ORM,而您通常不应该这样做。

Prepared statements/parameterized queries go further than just sanitizing or escaping the inputs.准备好的语句/参数化查询不仅仅是清理或转义输入。 When you use parameterized queries, the parameter data are sent as separate values from the SQL statement.当您使用参数化查询时,参数数据作为独立于 SQL 语句的发送。 The parameter data is never substituted directly into the SQL, and therefore injection is perfectly protected in a way that escaping/sanitizing the input never will.参数数据永远不会直接替换到 SQL 中,因此注入得到了完美的保护,永远不会对输入进行转义/清理。

In other words, DON'T COUNT ON STRING INTERPOLATION FOR "FIXING" SQL PARAMETERS!换句话说,不要指望“修复”SQL 参数的字符串插值!

Moreover, it's really not that much extra work.而且,这真的没有那么多额外的工作。 What the question shows is the hard way for adding parameters.问题显示的是添加参数的困难方法。 You can simplify that code like this:您可以像这样简化该代码:

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();
    }

    ...
}

This gets the extra work for parameters down to one line of code per parameter.这使参数的额外工作减少到每个参数一行代码。

If you have high-confidence in the mapping between C# types and SQL types, you can simplify even further like this:如果你对 C# 类型和 SQL 类型之间的映射有高度的信心,你可以像这样进一步简化:

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

Just be careful with that shortcut: if ADO.Net guesses the SQL data type wrong, it can break indexing and force per-row type conversions, which can really kill performance.请小心使用该快捷方式:如果 ADO.Net 猜测 SQL 数据类型错误,它可能会破坏索引并强制按行进行类型转换,这真的会降低性能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM