【问题标题】:C# SQL Server - Passing a list to a stored procedureC# SQL Server - 将列表传递给存储过程
【发布时间】:2014-09-26 21:27:25
【问题描述】:

我正在从我的 C# 代码调用 SQL Server 存储过程:

using (SqlConnection conn = new SqlConnection(connstring))
{
   conn.Open();
   using (SqlCommand cmd = new SqlCommand("InsertQuerySPROC", conn))
   {
      cmd.CommandType = CommandType.StoredProcedure;

      var STableParameter = cmd.Parameters.AddWithValue("@QueryTable", QueryTable);
      var NDistanceParameter = cmd.Parameters.AddWithValue("@NDistanceThreshold", NDistanceThreshold);
      var RDistanceParameter = cmd.Parameters.AddWithValue(@"RDistanceThreshold", RDistanceThreshold);

      STableParameter .SqlDbType = SqlDbType.Structured;
      NDistanceParameter.SqlDbType = SqlDbType.Int;
      RDistanceParameter.SqlDbType = SqlDbType.Int;

      // Execute the query
      SqlDataReader QueryReader = cmd.ExecuteReader();

我的存储过程是相当标准的,但与QueryTable 连接(因此需要使用存储过程)。

现在:我想将字符串列表List<string> 添加到参数集。例如,我的存储过程查询是这样的:

SELECT feature 
FROM table1 t1 
INNER JOIN @QueryTable t2 ON t1.fid = t2.fid 
WHERE title IN <LIST_OF_STRINGS_GOES_HERE>

但是,字符串列表是动态的并且有几百长。

有没有办法将字符串列表List&lt;string&gt; 传递给存储的过程???或者有更好的方法吗?

非常感谢, 布雷特

【问题讨论】:

  • SQL Server 的版本是什么? 2005 年?? 2008 年?? 2008 R2 ?? SQL Server 2008 及更高版本具有“表值参数”的概念(有关详细信息,请参阅 Redth 的回复)
  • 无关提示:SqlDataReader 也是 IDisposable 所以应该在 using 块中。

标签: c# sql stored-procedures


【解决方案1】:

如果您使用的是 SQL Server 2008,则有一个称为用户定义表类型的新功能。以下是如何使用它的示例:

创建您的用户定义表类型:

CREATE TYPE [dbo].[StringList] AS TABLE(
    [Item] [NVARCHAR](MAX) NULL
);

接下来你需要在你的存储过程中正确使用它:

CREATE PROCEDURE [dbo].[sp_UseStringList]
    @list StringList READONLY
AS
BEGIN
    -- Just return the items we passed in
    SELECT l.Item FROM @list l;
END

最后这里有一些在c#中使用的sql:

using (var con = new SqlConnection(connstring))
{
    con.Open();

    using (SqlCommand cmd = new SqlCommand("exec sp_UseStringList @list", con))
    {
        using (var table = new DataTable()) {
          table.Columns.Add("Item", typeof(string));

          for (int i = 0; i < 10; i++)
            table.Rows.Add("Item " + i.ToString());

          var pList = new SqlParameter("@list", SqlDbType.Structured);
          pList.TypeName = "dbo.StringList";
          pList.Value = table;

          cmd.Parameters.Add(pList);

          using (var dr = cmd.ExecuteReader())
          {
            while (dr.Read())
                Console.WriteLine(dr["Item"].ToString());
          }
         }
    }
}

从 SSMS 执行此操作

DECLARE @list AS StringList

INSERT INTO @list VALUES ('Apple')
INSERT INTO @list VALUES ('Banana')
INSERT INTO @list VALUES ('Orange')

-- Alternatively, you can populate @list with an INSERT-SELECT
INSERT INTO @list
   SELECT Name FROM Fruits

EXEC sp_UseStringList @list

【讨论】:

  • 是否必须定义一个数据表来设置参数的值?还有其他轻量级的方法吗?
  • 我们试过了,但发现它的缺点是 Enitiy 框架不支持
  • 如果您使用的是 LINQ2SQL,请谨慎使用此解决方案,因为它不支持用户定义的表类型作为参数!!! 可以在 Jon Raynor 的回答中找到解决方法,使用逗号分隔的列表和解析器函数,但这也有缺点......
  • @Fazi 在这种情况下,不要使用 Linq2SQL。最好在 T-SQL 中进行字符串连接和解析
  • 是的,那么您如何从 SSMS 实际执行此操作?
【解决方案2】:

这种情况下的典型模式是传递逗号分隔列表中的元素,然后在 SQL 中将其拆分为您可以使用的表。大多数人通常会为此创建一个指定的函数,例如:

 INSERT INTO <SomeTempTable>
 SELECT item FROM dbo.SplitCommaString(@myParameter)

然后你可以在其他查​​询中使用它。

【讨论】:

  • 为了完整性,我提供一个 dbo.SplitCommaString 实现的链接:goo.gl/P9ROs
  • 当您的某个数据字段中有逗号时会发生什么?
  • 用管道代替它。
  • @AlexInParis 如果您的某个数据字段中有管道怎么办?
  • 然后使用不在您的数据字段中的内容。如有必要,清理您的数据,但我见过的数据很少使用管道。如果绝对有必要,请找到其他字符,例如 ¤ 或 §。
【解决方案3】:

不,数组/列表不能直接传递给 SQL Server。

以下选项可用:

  1. 传递逗号分隔的列表,然后在 SQL 中使用函数拆分列表。逗号分隔的列表很可能会作为 Nvarchar() 传递
  2. 传递 xml 并在 SQL Server 中有一个函数为列表中的每个值解析 XML
  3. 使用新定义的用户定义表类型 (SQL 2008)
  4. 动态构建 SQL 并将原始列表作为“1,2,3,4”传入并构建 SQL 语句。这很容易受到 SQL 注入攻击,但它会起作用。

【讨论】:

  • 这是对可能选项的一个很好的总结。在我看来,第三个选项是最好的选择,不要为此使用第四个选项。
【解决方案4】:

是的,将存储过程参数设为VARCHAR(...) 然后将逗号分隔的值传递给存储过程。

如果您使用的是 Sql Server 2008,您可以利用 TVP (Table Value Parameters):SQL 2008 TVP and LINQ 如果 QueryTable 的结构比字符串数组更复杂,否则将是一个矫枉过正,因为需要在 SQl Server 中创建表类型

【讨论】:

    【解决方案5】:

    用一列而不是列表创建一个数据表,并将字符串添加到表中。您可以将此数据表作为结构化类型传递,并与表的标题字段执行另一个联接。

    【讨论】:

    • 这就是要走的路。我实际上在 db 端创建了一个表并加载了 bcp write to server。
    【解决方案6】:

    如果您更喜欢在 SQL 中拆分 CSV 列表,可以使用 Common Table Expressions (CTE) 以另一种方式来实现。见Efficient way to string split using CTE

    【讨论】:

    • 谢谢。改为堆栈溢出问题。
    【解决方案7】:

    我知道的唯一方法是构建 CSV 列表,然后将其作为字符串传递。然后,在 SP 方面,只需拆分它并做任何你需要的事情。

    【讨论】:

      【解决方案8】:
      CREATE TYPE [dbo].[StringList1] AS TABLE(
      [Item] [NVARCHAR](MAX) NULL,
      [counts][nvarchar](20) NULL);
      

      创建一个 TYPE 作为表并将其命名为“StringList1”

      create PROCEDURE [dbo].[sp_UseStringList1]
      @list StringList1 READONLY
      AS
      BEGIN
          -- Just return the items we passed in
          SELECT l.item,l.counts FROM @list l;
          SELECT l.item,l.counts into tempTable FROM @list l;
       End
      

      如上创建一个过程并将其命名为“UserStringList1” s

      String strConnection = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString.ToString();
                  SqlConnection con = new SqlConnection(strConnection);
                  con.Open();
                  var table = new DataTable();
      
                  table.Columns.Add("Item", typeof(string));
                  table.Columns.Add("count", typeof(string));
      
                  for (int i = 0; i < 10; i++)
                  {
                      table.Rows.Add(i.ToString(), (i+i).ToString());
      
                  }
                      SqlCommand cmd = new SqlCommand("exec sp_UseStringList1 @list", con);
      
      
                          var pList = new SqlParameter("@list", SqlDbType.Structured);
                          pList.TypeName = "dbo.StringList1";
                          pList.Value = table;
      
                          cmd.Parameters.Add(pList);
                          string result = string.Empty;
                          string counts = string.Empty;
                          var dr = cmd.ExecuteReader();
      
                          while (dr.Read())
                          {
                              result += dr["Item"].ToString();
                              counts += dr["counts"].ToString();
                          }
      

      在c#中,试试这个

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-20
        • 2014-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多