在这篇文章中有这么一段:

  这里作者有一句话:”不过这种写法和直接拼SQL执行没啥实质性的区别”
  任何拼接SQL的方式都有SQL注入的风险,所以如果没有实质性的区别的话,那么使用exec 动态执行SQL是不能防止SQL注入的。
  比如下面的代码:

private static void TestMethod()
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//使用exec动态执行SQL
//实际执行的查询计划为(@UserID varchar(max))select * from Users(nolock) where UserID in (1,2,3,4)
//不是预期的(@UserID varchar(max))exec('select * from Users(nolock) where UserID in ('+@UserID+')')
comm.CommandText = "exec('select * from Users(nolock) where UserID in ('+@UserID+')')";
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4" });
//comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4); delete from Users;--" });
comm.ExecuteNonQuery();
}
}
  执行的SQL 如下:
  exec sp_executesql N'exec(''select * from Users(nolock) where UserID in (''+@UserID+'')'')',N'@UserID varchar(max) ',@UserID='1,2,3,4'
 
  可以看到SQL语句并没有参数化查询。
  如果你将UserID设置为”1,2,3,4); delete from Users;—-”,那么执行的SQL是下面这样:
  exec sp_executesql N'exec(''select * from Users(nolock) where UserID in (''+@UserID+'')'')',N'@UserID varchar(max) ',@UserID='1,2,3,4); delete from Users;--'
  不要以为加了个@UserID 代表能够防止SQL注入,实际执行的SQL 如下:
  任何动态的执行SQL 都有注入的风险,因为动态意味着不重用执行计划,而如果不重用执行计划的话,那么基本上无法保证你写的SQL所表示的意思是你要表达的意思。
  这好像小时候的填空题,查找密码是(____) 并且用户名是(____)的用户。
  不管你填的是什么值,我所表达的是这个意思。
  后再总结一句:因为参数化查询可以重用执行计划,并且如果重用执行计划的话,SQL所要表达的语义不会变化,所以可以防止SQL注入,如果不能重用执行计划,有可能出现SQL注入,存储过程也是一样的道理,因为可以重用执行计划。