【问题标题】:How to pass sqlparameter to IN()? [duplicate]如何将 sql 参数传递给 IN()? [复制]
【发布时间】:2012-03-12 04:04:42
【问题描述】:

由于某种原因,我的 IN() 子句的 Sqlparameter 不起作用。代码编译得很好,如果我用实际值替换参数,查询就可以工作

StringBuilder sb = new StringBuilder();
            foreach (User user in UserList)
            {
                sb.Append(user.UserId + ",");
            }

            string userIds = sb.ToString();
            userIds = userIds.TrimEnd(new char[] { ',' });


SELECT userId, username 
FROM Users 
WHERE userId IN (@UserIds) 

【问题讨论】:

标签: c# sql-server ado.net


【解决方案1】:

您必须为IN 子句中所需的每个值创建一个参数。

SQL 需要如下所示:

SELECT userId, username 
FROM Users 
WHERE userId IN (@UserId1, @UserId2, @UserId3, ...) 

因此您需要在foreach 循环中创建参数 IN 子句。
像这样的东西(在我脑海中,未经测试):

StringBuilder sb = new StringBuilder();
int i = 1;

foreach (User user in UserList)
{
    // IN clause
    sb.Append("@UserId" + i.ToString() + ",");

    // parameter
    YourCommand.Parameters.AddWithValue("@UserId" + i.ToString(), user.UserId);

    i++;
}

【讨论】:

  • 记得去掉最后一个逗号,因为上面的例子是在 (@userId,)
  • 这是一篇非常古老的帖子,但只需再更新一次即可回答。帮助另一个 googler,;) 您可以使用 xml 作为参数来获得 CSV 和 in 子句查询的类似效果 - SELECT userId, username FROM Users u1 INNER JOIN @UsersID.nodes('/ID') T(col) ON u1.userId = t.col.value('.', 'int')
  • 对任何使用它的人发出巨大警告。 SQL Server 默认使用 2100 作为参数的最大数量。
【解决方案2】:

可能的“更清洁”版本:

StringBuilder B = new StringBuilder();
for (int i = 0; i < UserList.Count; i++)
     YourCommand.Parameters.AddWithValue($"@UserId{i}", UserList[i].UserId);
B.Append(String.Join(",", YourCommand.Parameters.Select(x => x.Name)));

【讨论】:

  • 我不明白。当您执行 YourCommand.Parameters.Select(x => x.Name) 时,您有一个类似的列表:John, Mary, ... 而不是 User1,User2,User3 的列表。
  • @LucaCosta 使用字符串插值获得 +1 和吹毛求疵我的“干净”代码的 -1 大声笑 j/k 干得好 :)
【解决方案3】:

如果您使用的是 SQL 2008,您可以创建一个接受表值参数 (TVP) 的存储过程,并使用 ADO.net 执行该存储过程并将数据表传递给它:

首先,您需要在 SQL server 中创建类型:

CREATE TYPE [dbo].[udt_UserId] AS TABLE(
    [UserId] [int] NULL
)

然后,您需要编写一个接受此类型作为参数的存储过程:

CREATE PROCEDURE [dbo].[usp_DoSomethingWithTableTypedParameter]
(
   @UserIdList udt_UserId READONLY
)
AS
BEGIN

        SELECT userId, username 
        FROM Users 
        WHERE userId IN (SELECT UserId FROM @UserIDList) 

END

现在从 .net 开始,您不能使用 LINQ,因为它还不支持表值参数;所以你必须编写一个函数来执行普通的旧 ADO.net,获取一个 DataTable,并将其传递给存储过程:我编写了一个通用函数,我使用它可以对任何存储过程执行此操作,只要它只需要一个表格类型的参数,不管它是什么;

    public static int ExecStoredProcWithTVP(DbConnection connection, string storedProcedureName, string tableName, string tableTypeName, DataTable dt)
    {
        using (SqlConnection conn = new SqlConnection(connection.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand(storedProcedureName, conn);
            cmd.CommandType = CommandType.StoredProcedure;

            SqlParameter p = cmd.Parameters.AddWithValue(tableName, dt);
            p.SqlDbType = SqlDbType.Structured;
            p.TypeName = tableTypeName;

            conn.Open();
            int rowsAffected = cmd.ExecuteNonQuery(); // or could execute reader and pass a Func<T> to perform action on the datareader;
            conn.Close();

            return rowsAffected;
        }
    }

然后你可以编写 DAL 函数,使用这个实用函数和存储过程的实际名称;以您问题中的示例为基础,代码如下所示:

    public int usp_DoSomethingWithTableTypedParameter(List<UserID> userIdList)
    {
        DataTable dt = new DataTable();
        dt.Columns.Add("UserId", typeof(int));

        foreach (var userId in updateList)
        {
            dt.Rows.Add(new object[] { userId });
        }

        int rowsAffected = ExecStoredProcWithTVP(Connection, "usp_DoSomethingWithTableTypedParameter", "@UserIdList", "udt_UserId", dt);
        return rowsAffected;
    }

注意上面的“连接”参数——我实际上在部分 DataContext 类中使用这种类型的函数来扩展 LINQ DataContext 和我的 TVP 功能,并且仍然使用(使用 var context = new MyDataContext())这些方法的语法.

这只有在您使用 SQL Server 2008 时才有效 - 希望您是,如果不是,这可能是升级的好理由!当然,在大多数情况下和大型生产环境中,这并不容易,但 FWIW 我认为如果你有可用的技术,这是最好的方法。

【讨论】:

  • 如果您的大多数表都使用相同的类型进行 PK,您能否不做一个通用的可重用 ID 参数 UDT?像:CREATE TYPE [dbo].[udt_IntId] AS TABLE([Id] [int] NULL) 在 any 需要对 int 主键 id 执行 sql IN 子句的情况下重复使用?
【解决方案4】:

SQL Server 将您的 IN 子句视为:

IN ('a,b,c')

它需要是什么样子的:

IN ('a','b','c')

有更好的方法来做你想做的事。

  • 如果用户 ID 在 DB 中,则应将 IN 子句更改为子查询,如下所示:

    IN (SELECT UserID FROM someTable WHERE someConditions)

  • 这是一个 hack —— 它不适用于索引,您必须小心它适用于您的数据,但我过去曾成功使用过它:

    @UserIDs LIKE '%,' + UserID + ',%' -- also requires @UserID to begin and end with a comma

【讨论】:

  • +1 为您的 hack。即使它可能会强制进行全面扫描,并阻止优化器完成其工作,这也是一个聪明的技巧,它也适用于 Access。
  • @John:我试过这个:IN (@param) 然后command.Parameters.AddWithValue("@param", "'a','b','c'"); 但这不成功。能否请您对此提出建议。
  • @user1671639 如果你总是有 3 个参数,那么你可以使用IN (@param1, @param2, @param3) 然后command.Parameters.AddWithValue("@param1", "a"); command.Parameters.AddWithValue("@param2", "b"); command.Parameters.AddWithValue("@param3", "c");。如果您并不总是有 3 个值,也许您应该问一个新的 stackoverflow.com 问题,提供足够的详细信息,然后将我指向新问题。我敢打赌有几个人会马上回答。
  • @John:谢谢。但是我找到了一种简单的方法,它可以完美地工作string param="'a','b','c'"; 然后"SELECT * FROM table WHERE IN (" + param + ")";。请告知这是否正确。
  • @user1671639 如果 a、b 和 c 是用户输入,那么您的代码很容易受到 SQL 注入攻击。这就是为什么您应该改用 Parameter 对象的原因。如果您想进一步讨论这个问题,请创建一个新的 stackoverflow.com 问题。
猜你喜欢
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
  • 2016-01-12
  • 2011-06-11
  • 2019-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多