【问题标题】:'Must Declare the Scalar Variable' Error When Passing a Table-Valued Parameter to a Parameterized SQL Statement将表值参数传递给参数化 SQL 语句时出现“必须声明标量变量”错误
【发布时间】:2021-05-09 22:36:29
【问题描述】:

在 C# 中,我试图将 DataTable 作为参数传递给 SQL 语句。我的代码如下:

protected virtual void DoDeleteRecords(List<Guid> ids)
{   
    if (ids.Count > 0)
    { 
        DataTable tvp = new DataTable();
        tvp.Columns.Add("Id", typeof(Guid));

        foreach (Guid id in ids)
        {
            DataRow row = tvp.NewRow();
            row["Id"] = id;

            tvp.Rows.Add(row);
        }

        string sql = string.Format("DELETE FROM MyTable WHERE ID IN ({0})", "@IDTable");

        SqlConnection connection = new SqlConnection(CoreSettings.ConnectionString);

        using (connection)
        {
            SqlCommand command = new SqlCommand(sql, connection);
            SqlParameter tvpParam = command.Parameters.AddWithValue("@IDTable", tvp);
            tvpParam.SqlDbType = SqlDbType.Structured;
            tvpParam.TypeName = "dbo.IDList";

            connection.Open();

            command.ExecuteNonQuery();

            connection.Close();
        }
    }
}

但是,当 command.ExecuteNonQuery 被调用时,我得到以下 SqlException 错误:

必须声明标量变量“@IDTable”

我了解此错误通常与丢失参数值有关,但据我所知,我知道。

谁能看出我做错了什么?

非常感谢。

更新我已经修改了问题以从我的示例中删除糟糕的 SQL 注入丰富的代码。

【问题讨论】:

  • 您不能在 ad-hoc 语句中传递 TVP。这种方式仅支持存储过程参数。考虑使用 Dapper;这是无趣的样板,Dapper 确实支持构建列表。
  • 啊,好的。那么这将解释它!谢谢@JeroenMostert
  • tableName 是 TVP 时,"DELETE FROM " + tableName + " WHERE..." 还试图实现什么?这会在子句中注入一个标量值。此外,这种类型的语法是一种可怕的安全性漏洞。现在是 2021 年,SQL 注入应该在几年前就已经死了。
  • @JeroenMostert 那是垃圾,你当然可以。但是没有使用 TVP,OP 只是将一个列表直接连接到查询中。正确的代码是"DELETE FROM " + tableName + " WHERE " + idColumnName + " IN (SELECT * FROM @IDTable)" 不需要string.Format。无论如何,这段代码看起来很危险,对 SQL 注入完全开放
  • @Charlieface 你是绝对正确的。我错过了来自 IN 的 SELECT。该死的!感谢您发现这一点。为了清楚起见,这不是生产代码——只是我用来测试一些想法的东西。不,其中一个想法不是“如何将我的 SQL 注入到最大”!

标签: c# sql-server sqlcommand sqlexception table-valued-parameters


【解决方案1】:

第一件事:我不知道你从哪里得到tableNamecolumnName,但如果它们是用户提供的,那么这对SQL 注入是开放的。至少,使用QUOTENAME() 确保没有实际代码被注入。

其次,您实际上并未使用 TVP。您拥有的代码只是说 IN (@IDTable),这不是您使用 TVP 的方式。

TVP 只是一个表变量,应该像任何其他表一样使用:

protected virtual void DoDeleteRecords(List<Guid> ids)
{   
    if (ids.Count == 0)
        return;
    DataTable tvp = new DataTable();
    tvp.Columns.Add("Id", typeof(Guid));

    foreach (Guid id in ids)
        tvp.Rows.Add(id);

    const string sql = @"
DELETE FROM table
WHERE idColumnName IN (SELECT * FROM @IDTable);
";

    using(SqlConnection connection = new SqlConnection(CoreSettings.ConnectionString))
    using(SqlCommand command = new SqlCommand(sql, connection))
    {
        command.Parameters.Add(
            new SqlParameter("@IDTable", SqlDbType.Structured)
        {
            Value = tvp,
            Direction = ParameterDirection.Input,
            TypeName = "dbo.IDList"
        });

        connection.Open();
        command.ExecuteNonQuery();
    }
}

【讨论】:

  • 你说的很对,我发布的原始代码是垃圾,我现在修改了我的问题,将表/列名称硬编码到 SQL 语句中。没有真正的理由将它们作为变量连接。另外,感谢您发现我对 TVP 表的错误。一个真正的面对面的时刻!
  • 相信你也需要ParameterDirection.Input(相当于SQL中的READONLY),已添加
猜你喜欢
  • 1970-01-01
  • 2017-03-07
  • 1970-01-01
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 2014-01-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多