SqlCommand参数 vs. String.Format

5

我一直在网上搜索,但似乎找不到任何解释我的问题的东西(可能是我没有使用正确的搜索字符串),所以我在这里发帖,希望有人能帮助我。(我的程序是使用Visual Studio 2010编写的C#)

我注意到在C#中,有多种构建SQL命令的方法。

SqlConnection connection = GetAndOpenConnection(); //function containing connection string and open connection
SqlCommand command = connection.CreateCommand();

到这个地方,我还没有问题。我的问题在于 CommandText。我在代码中使用了几个不同的命令(SELECT/INSERT/UPDATE/DELETE),但是我们以 SELECT 为例。

//Example 1:
command.CommandText = String.Format("SELECT * FROM myTable WHERE name = '{0}'", "bob");

//Example 2:
command.CommandText = "SELECT * FROM myTable WHERE name = @myName";
command.Parameters.Add(new SqlParameter("myName", "bob"));

上面两个例子有什么不同之处?(性能/结构等方面)我问这个问题是因为在同一个.cs文件中,当我使用第二个示例中的方法时,有时代码可以正常工作,有时不能正常工作,然后最终我会使所有内容都像第一个示例一样,每次都能正常工作。使用任何一种方法是否存在显着的收益/损失?哪种方法更适合完成此类任务?
附加问题:好的,我看到方法2是更合适的方法。然而,如果我使用方法2就会出现问题。我有一个循环,循环遍历List names。在循环内部,当我使用方法2并将名称添加为参数时,我会收到一个错误,说该参数已经存在,无法添加。如何解决这个问题?
List<string> names = new List<string> {"adam", "bob", "john"};
foreach(string name in names)
{
    command.CommandText = "SELECT * FROM myTable WHERE name = @myName";
    command.Parameters.Add(new SqlParameter("myName", name));
    reader = command.ExecuteReader();

    while(reader.Read())
    {
        //loop through each cell and print on the Console
    }
}

另外,我知道有人提到在参数中应该使用"@myName"而不是"myName"。我记得当时遇到了这个问题,因为我不确定该采用哪种方式,所以只能进行测试。在我使用方法2的部分中,"@myName"不能正常工作,但"myName"可以,而且现在代码中仍然是这样的。我正在使用.Net 4.0,不知道是否会有影响。

另外,您没有正确添加参数,应该在添加参数时以 @ 开头。 - Habib
http://bobby-tables.com/ - Habib
你能展示一下额外问题的代码吗? - Andrei
@Andrei,请查看我的更新。 - sora0419
在下面回答了您的编辑。 - Evan L
除了给你的代码命名之外,还存在命令实例使用的问题。请查看更新以获取更多详细信息。 - Andrei
4个回答

9
存在参数以防止 SQL 注入。例如,考虑一下如果在 string.Format 中使用 TextBox1.Text 而非 bob,其中包含了 1';DROP TABLE myTable;',会发生什么情况。
如果您对参数拥有完全的控制权,比如说使用参数的字符串字面量,在这种情况下就不可能发生 SQL 注入。然而,您永远不知道您的代码将来会如何变化,因此,一般来说,您应该始终坚持更安全的参数处理方法。
如果您在使用第二种方法时遇到了特定的问题,请搜索或者在此处发布,很可能已经有解决方案了。例如,在您的代码片段中,实际的参数名是带有 @ 符号的 @myName,这也是应该提供给 SqlParameter 构造函数的。
更新:在您的附加问题中,问题恰好在于参数命名——应该使用 @myName
command.Parameters.Add(new SqlParameter("@myName", name));

此外,您应该在每次迭代中清除参数集合:

command.Parameters.Clear();

虽然为了避免混乱最好在每次迭代中创建新的命令,但是可以查看此线程获取详细信息。


我可能错了,但是我认为在每次迭代中创建命令会占用太多空间(List<string> names非常大,我只在问题中放了一个小样本),这就是为什么我在循环外声明了SqlCommand,并且只在循环内更改CommandText。我将尝试你提供的方法。 - sora0419
@sora0419,未使用的对象将被垃圾回收并不会对内存造成任何问题,特别是如果您将它们包装在using中以进行适当处理。 - Andrei

5

如上所述,第一个(动态)查询容易受到SQL注入攻击。此外,每次执行时都必须重新编译它,这会增加执行成本(并可能在编译过程中阻塞)。

第二个(参数化)查询不容易受到SQL注入攻击。此外,它的执行计划可以被 缓存,因此只有在第一次执行时才编译。至少在缓存到期或由于某种原因而被清除之前。


3
以上所有答案都是正确的,但是针对你的编辑,你需要在每次迭代使用完命令后将其处理掉:
您需要在每次迭代使用完命令后将其处理掉。
using (var conn = new SqlConnection(connectionString)
{
    conn.open(); //You only need to open the connection once, so we do it outside the loop
    foreach (var name in names)
    {
        using (var cmd = new SqlCommand("SELECT * FROM myTable where name = @MyName", conn)
        {
            cmd.Parameters.AddWithValue("@MyName", name);
            //Do something with the command
        }
        //The command is disposed of here
    }
}
//The connection is disposed of here.

3

方法一:容易受到SQL注入攻击

方法二:更加安全的SQL执行方式,需要稍微多做一点工作

顺便提一下,正确的写法是 command.Parameters.Add(new SqlParameter("@myName", "bob"));


我记得曾经被这个搞糊涂了。我不确定是"@myName"还是"myName",必须进行测试。 "myName" 是我实际使用的那个,并且在我的代码中仍然使用方法2。(我使用的是.Net 4.0,不确定是否有影响。) - sora0419

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接