【问题标题】:Add List<int> to a mysql parameter将 List<int> 添加到 mysql 参数
【发布时间】:2011-08-06 13:59:00
【问题描述】:

我有这个关于 .NET 连接器的 MySqlParameter 的问题。

我有这个问题:

SELECT * FROM table WHERE id IN (@parameter)

而MySqlParameter是:

intArray = new List<int>(){1,2,3,4};

...connection.Command.Parameters.AddWithValue("parameter", intArray);

这可能吗? 是否可以将 int 数组传递给单个 MySqlParameter? 另一种解决方案是将int数组转换为诸如“1,2,3,4”之类的字符串,但是,当我将它传递给MySqlParameter并且这被识别为字符串时,它会像这样放入sql查询"1\,2\,3\,4" 这不会返回预期值。

@ UPDATE: mysql 连接器团队似乎应该更加努力。

【问题讨论】:

  • 不仅仅是 MySQL。据我所知,这在 MSSQL 中也不支持,也不是 T-SQL 规范的一部分。
  • NHibernate 和其他 ORM 给人的印象是支持这一点,但在后台他们正在执行字符串连接(带或不带参数)

标签: c# .net mysql mysql-connector mysql-parameter


【解决方案1】:

基于Richard 的回答,我想出了一个类似的方法来避免 SQL 注入:

private MySqlCommand GetCommandWithIn(string sql, string sqlParam, ICollection<string> list)
{
    var command = new MySqlCommand(string.Empty, connection);
    var i = 0;
    var parameters = new List<string>(list.Count);

    foreach (var element in list)
    {
        var parameter = $"p{i++}";
        parameters.Add($"@{parameter}");
        command.Parameters.AddWithValue(parameter, element);
    }

    command.CommandText = sql.Replace(sqlParam, string.Join(',', parameters));

    return command;
}

你可以这样称呼它:

var types = new List<string> { "Type1", "Type2" };
var sql = "SELECT * FROM TABLE WHERE Type IN (@Type)"
using var command = GetCommandWithIn(sql, "@Type", types);

记得检查空列表以避免IN ()

【讨论】:

    【解决方案2】:

    这里有几个选项(按优先顺序排列):

    1. 使用支持table valued parameters 的数据库。这是获得您想要的确切语法的唯一方法。
    2. 数据必须来自某个地方:您的数据库、用户操作或机器生成的来源。

      • 如果数据已经在您的数据库中,请改用子查询。
      • 对于其他机器生成的数据,请使用 BULK INSERT、SqlBulkCopy 或您的数据库首选的批量导入工具。
      • 如果它是由用户创建的,请将其添加到单独的表中针对每个单独的用户操作,然后使用子查询。

        购物车就是一个例子。用户可能会选择几个项目来购买。与其将这些保留在应用程序中,并且需要在结账时一次性将所有项目添加到订单中,不如在用户选择或更改时将每个项目添加到数据库中的表中。

    3. 有一个 sql 用户定义函数,它可以将字符串参数解压缩到一个表中,并将该表作为一个集合返回,您可以将其与 IN() 表达式一起使用。有关其工作原理的更多详细信息,请参阅下面的链接文章。
    4. 在客户端动态构建字符串列表或参数列表(如其他答案所示)。请注意,这是我最不喜欢的选项,因为它创建的代码往往容易受到 sql 注入问题的影响。

    关于这个主题的权威(我的意思是权威)工作在这里:

    http://www.sommarskog.se/arrays-in-sql.html

    文章很长,但方式很好。作者是 sql server 专家,但总体概念也适用于 MySQL。

    【讨论】:

      【解决方案3】:

      Answer from Mud 仅适用于参数列表中的第一个 int。这意味着如果 id 为 1,则 '2,1,3,4' 将不起作用。

      FIND_IN_SET() vs IN()

      目前无法发表评论,但请参阅 Matt Ellen 的回答。 会编辑他的答案,但不能。 INSTR 似乎不适用于具有多个 id 的 WHERE 情况(仅返回结果)。

      但是用LOCATE 替换INSTR 使他的解决方案起作用(添加String.Join(",", intArray) 作为参数)...来自我的投票:

      LOCATE(CONCAT(',' , CAST(id AS CHAR) , ',') , CONCAT(',' , CAST(@paramter AS CHAR) , ',')) <> 0
      

      【讨论】:

        【解决方案4】:

        当我将它传递给 MySqlParameter 并且这被识别为字符串时,它会像 "1\,2\,3\,4" 这样放入 sql 查询中,并且不会返回预期值。

        我昨晚遇到了这个。我发现 FIND_IN_SET 在这里工作:

        SELECT * FROM table WHERE FIND_IN_SET(id, @parameter) != 0
        ...
        intArray = new List<int>(){1,2,3,4};
        conn.Command.Parameters.AddWithValue("parameter", string.Join(",", intArray));
        

        显然这有一些长度限制(我发现您的帖子正在寻找替代解决方案),但这可能对您有用。

        【讨论】:

        • 前面的评论有误导性... Mud 正在使用参数化查询来实现目标。参数化查询实际上是您用来防止字符串中的 sql 注入而无需手动清理字符串内容的方法。这是实现目标的一个很好的例子。
        • @NickHanshaw 你当然是对的。我相信我可能将这与关于 ORM 和字符串连接的原始问题的评论混为一谈。无论如何,我将删除我的评论以避免任何混淆。很好的收获,谢谢。索引仍然是一个问题,但根据表可能没问题。
        【解决方案5】:

        这不适用于大型列表,但这是我发现必须将列表作为参数传入的唯一东西。

        而不是

        SELECT * FROM table WHERE id IN (@parameter)
        

        你必须这样做:

        SELECT *
        FROM table
        WHERE INSTR(','+@parameter+',', ','+CAST(the_column AS CHAR) + ',')
        

        然后您可以使用string.Join(",", intArray) 传递您的列表

        这是一个杂牌,但它有效。

        【讨论】:

          【解决方案6】:

          您将不得不遍历您的数组并自己创建列表

          // no parameters
          var sb = new StringBuilder();
          for(int i=0;i<intArray.Length;i++)
          {
              sb.Append(intArray[i] + ",");// no SQL injection they are numbers
          }
          if (sb.Length>0) {sb.Length-=1;}
          string sql = "SELECT * FROM table WHERE id IN (" + sb.ToString() + ")";
          

          更新:在考虑了更多之后,我将回到我原来的答案(如下),即使用参数。构建查询的优化以及数据库引擎可以收集的任何内容都取决于您。

          // no parameters
          var sb = new StringBuilder();
          for(int i=0;i<intArray.Length;i++)
          {
              sb.AppendFormat("p{0},", i);// no SQL injection they are numbers
              connection.Command.Parameters.AddWithValue("p"+i, intArray[i]);
          }
          if (sb.Length>0) {sb.Length-=1;}
          string sql = "SELECT * FROM table WHERE id IN (" + sb.ToString() + ")";
          

          【讨论】:

          • 整个循环和检查可以替换为string.Join(",", intArray)
          • 好吧,是的,如果您想要简短易读的代码 :-) 而不是在我的情况下忘记该方法
          • 有 sql 注入的味道。即使您在 int 序列中使用相对“安全”的类型,在我看来,这也是一个坏习惯。
          • “sql 注入的味道”我同意,但我不喜欢这个,但它也是在某些情况下解决问题的唯一方法,而不需要做一些愚蠢的事情,比如迭代“id=@parm_x..”和结束。使用强类型数值数据绝对没有风险。避免像 sql 注入这样的问题的关键不仅仅是盲目相信以某种方式做事——它还知道你在做什么。
          • 包含逗号分隔的数字列表的字符串不能是我知道的任何 SQL 驱动程序中的参数值。请注意,在我的示例中,我正在设置 整个 SQL 值,没有参数。正如其他地方所讨论的,您不能使用 SQL 传递和数组。我会更详细地更新我的答案
          【解决方案7】:

          我认为没有办法可以像这样添加它们,但也许你可以遍历列表并动态生成查询。

          例如:

          var intArray = new List<int>(){1,2,3,4};
          if (intArray.Count > 0) {
              var query = "SELECT * FROM table WHERE id IN (";
              for (int i = 0; i < intArray.Count; i++) {
                  //Append the parameter to the query
                  //Note: I'm not sure if mysql uses "@" but you can replace this if needed
                  query += "@num" + i + ",";
                  //Add the value to the parameters collection
                  ...connection.Command.Parameters.AddWithValue("num" + i, intArray[i]);
              }
              //Remove the last comma and add the closing bracket
              query = query.Substring(0, query.Length - 1) + ");";
              //Execute the query here
          }
          

          这样,您甚至可以使用不同类型的列表,并且仍然可以从参数化查询中获益。但是,我不知道较大的列表是否会出现性能问题,但我怀疑会出现这种情况。

          【讨论】:

            【解决方案8】:

            参数不适用于 IN。我一直在查询本身中嵌入诸如字符串之类的东西。虽然由于 SQL 注入,这通常被认为是错误的形式,但如果您从强类型数字列表构造查询,那么任何外部输入都不应该以有意义的方式破坏它。

            【讨论】:

            • 也许我解释错了..当我说“将数组转换为字符串并传递它”时,我并不是说“SELECT * FROM table WHERE id IN(" + strings + ")”...
            • 我知道,我是说不可能按照你想要的方式去做。
            【解决方案9】:

            据我所知,您不能将任何数组作为参数提供给准备好的语句。 IN() 不支持将参数作为数组。

            【讨论】:

            • 你可以做 IN 的事情......做“where id IN (1,2,3,4)”与“where id = 1 or id = 2 or id = 3”相同或 id = 4",至少这对我有用 :)
            • @user710422 正如我在您的示例中看到的,没有要绑定的参数。我们只谈论它们。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-26
            • 2015-05-19
            • 1970-01-01
            • 2016-09-20
            • 1970-01-01
            相关资源
            最近更新 更多