【问题标题】:Parameterize WHERE Clause in Query在查询中参数化 WHERE 子句
【发布时间】:2013-08-30 04:06:21
【问题描述】:

环境:

  • C#
  • Visual Studio 2012
  • .NET Framework 3.5

我可以在 SQL Server 中参数化 where 子句吗?

在我的场景中,一旦输入了 WHERE 子句字符串,应用程序会将其连接到查询的其他部分并在 SQL Server 中执行,然后返回结果。

例如,

  • 用户输入“[CookingTime]
  • 应用程序创建查询“select [RecipeID] from [Recipes] where [CookingTime]
  • 应用程序将结果返回给用户。

出于安全原因,我想将整个 WHERE CLAUSE 作为参数。 但我不知道如何实现。

提前致谢。

【问题讨论】:

  • 可以创建动态查询
  • 动态查询在这里不是一个好的解决方案,您正在使用用户输入来创建查询,这是一个非常糟糕的主意并且对 sql 注入开放
  • @rs 根本不正确,您可以创建一个参数化的动态查询字符串。
  • 对缺乏解释表示歉意。在 where 子句中,将验证参数的数量。所以使用 SqlParameter 的标准方法不符合。当然使用连接 where 子句和其他部分会导致严重的安全问题(SQL 注入)。现在我正在尝试 ReX 的方式,并将返回结果。

标签: c# .net sql sql-server


【解决方案1】:

This是怎么做到的

string commandText = "UPDATE Sales.Store SET Demographics = @demographics "
    + "WHERE CustomerID = @ID;";

using (SqlConnection connection = new SqlConnection(connectionString))
{
    SqlCommand command = new SqlCommand(commandText, connection);
    command.Parameters.Add("@ID", SqlDbType.Int);
    command.Parameters["@ID"].Value = customerID;

    // Use AddWithValue to assign Demographics. 
    // SQL Server will implicitly convert strings into XML.
    command.Parameters.AddWithValue("@demographics", demoXml);

    try
    {
        connection.Open();
        Int32 rowsAffected = command.ExecuteNonQuery();
        Console.WriteLine("RowsAffected: {0}", rowsAffected);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

【讨论】:

  • 这并不能解决原始问题,因为 OP 需要参数化整个 where 子句
【解决方案2】:

作为参数的整个 WHERE 子句无论如何都会成为 sql 注入的受害者。为防止这种情况,您最好:

设置适当的权限。因此,即使在注入 sql 的情况下,用户也无法访问任何未授予的内容。在这种情况下,@Dhaval 的示例更好,因为封装在存储过程中的动态 sql 生成需要较少的权限来执行。

检查sql注入语句。最简单的方法是检查分号以避免批处理中的其他语句。更复杂更精确的方法是使用 t-sql DOM 解析器。例如:

using Microsoft.SqlServer.TransactSql.ScriptDom;

TSql110Parser parser = new TSql110Parser(true);
IList<ParseError> errors = null;
var condition = "a > 100; delete from [Recipes]";
var script = parser.Parse(new StringReader("select [RecipeID] from [Recipes] where " + condition), out errors) as TSqlScript;

if (errors.Count > 0)
{
    throw new Exception(errors[0].Message);
}

foreach (var batch in script.Batches)
{
    if (batch.Statements.Count == 1)
    {
        var select = batch.Statements[0] as SelectStatement;
        if (select != null)
        {
            QuerySpecification query = select.QueryExpression as QuerySpecification;
            if (query.WhereClause is BooleanBinaryExpression)
            {
                ...
            }
        }
        else
        {
            throw new Exception("Select statement only allowed");
        }
    }
    else
    {
        throw new Exception("More than one statement detected");
    }
}

【讨论】:

    【解决方案3】:

    您可以在 sql server 中创建动态查询并从 C# 传递参数

    类似的东西

    Create Procedure usp_Test    
        @WhereCond Varchar(max)
    AS
    Bgein
        Set NoCount ON
        Declare @SQLQuery AS Varchar(max)
        Set @SQLQuery = 'Select * From tblEmployees where ' + @WhereCond
        Execute sp_Executesql @SQLQuery
    
    End
    

    执行过程的C#代码

    DataSet ds = new DataSet();
        using(SqlConnection conn = new SqlConnection("ConnectionString"))
        {               
                SqlCommand sqlComm = new SqlCommand("usp_Test", conn);               
                sqlComm.Parameters.AddWithValue("@WhereCond", WhereCond);
    
                sqlComm.CommandType = CommandType.StoredProcedure;
    
                SqlDataAdapter da = new SqlDataAdapter();
                da.SelectCommand = sqlComm;
    
                da.Fill(ds);
         }
    

    【讨论】:

      【解决方案4】:

      我猜最初的问题是想找出如何从用户的输入中动态生成它,然后使用适当的 sql 参数进行查询。

      对于sql参数的使用,通常我做的是使用一个通用的辅助方法,一个简单的例子(未测试):

      public static class SqlHelpers
      {
          public static IEnumerable<T> ExecuteAdhocQuery<T>(SqlConnection con, string sql, CommandType cmdType, Func<SqlDataReader, T> converter, params SqlParameter[] args)
          {
              try
              {
                  using (SqlCommand cmd = new SqlCommand(sql, con) { CommandType = cmdType })
                  {
                      cmd.Parameters.AddRange(args);
      
                      if (con.State != ConnectionState.Open) { con.Open(); }
      
                      var ret = new List<T>();
      
                      using (SqlDataReader rdr = cmd.ExecuteReader())
                      {
                          while (rdr.Read())
                          {
                              ret.Add(converter.Invoke(rdr));
                          }
                      }
      
                      return ret;
                  }
              }
              catch (Exception e)
              {
                  // log error?
                  Console.WriteLine(e.Message);
                  Console.WriteLine(e.StackTrace);
                  throw e; // handle exception...
              }
          }
      
          public void Test()
          {
              using (SqlConnection con = new SqlConnection("connection string here"))
              {
                  var data = ExecuteAdhocQuery(con,
                      "SELECT ID, Name FROM tblMyTable WHERE ID = @Id and Status = @Status;",
                      CommandType.Text, (x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
                      new SqlParameter("@Id", SqlDbType.Int) { Value = 1 },
                      new SqlParameter("@Status", SqlDbType.Bit) { Value = true });
                  Console.WriteLine(data.Count());
              }
          }
      }
      

      当然,这只是Reading,对于Insert/Update,也可以创建类似的方法。

      但复杂的部分是如何在未知数量的条件以及它们之间的关系下使其动态化。所以一个快速的建议是使用委托方法或类来完成这项工作。样品(未测试):

          public static Dictionary<string, SqlParameter> GetParamsFromInputString(string inputString)
          {
              var output = new Dictionary<string, SqlParameter>();
      
              // use Regex to translate the input string (something like "[CookingTime] < 30 and [Cost] < 20" ) into a key value pair
              // and then build sql parameter and return out
              // The key will be the database field while the corresponding value is the sql param with value
      
              return output;
          }
      
          public void TestWithInput(string condition)
          {
              var parameters = GetParamsFromInputString(condition);
      
              // first build up the sql query:
              var sql = "SELECT Id, Name from tblMyTable WHERE " + parameters.Select(m => string.Format("{0}={1}", m.Key, m.Value.ParameterName)).Aggregate((m,n) => m + " AND " + n);
              using (SqlConnection con = new SqlConnection("connection string here"))
              {
                  var data = ExecuteAdhocQuery(con,
                      sql,
                      CommandType.Text,
                      (x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
                      parameters.Select(m => m.Value).ToArray());
              }
          }
      

      对于静态函数 GetParamsFromInputString,它只是一个示例。实际上,根据您的需要,它可能会非常复杂。

      例如,您可能希望包含运算符(无论是 >、、...)。

      并且您可能还希望包括条件之间的连词,无论是 AND 还是 OR。

      如果工作非常复杂,请构建委托类来完成这项工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多