这里应该使用表值参数吗?

Posted

技术标签:

【中文标题】这里应该使用表值参数吗?【英文标题】:Should table-valued parameters be used here? 【发布时间】:2013-04-16 14:50:49 【问题描述】:

我有以下查询:

 @"UPDATE students SET IsDeleted = 1 WHERE StudentId IN (
         SELECT StudentId FROM Class where PassId IN (
              SELECT Id FROM ClassValueTable WHERE IsDeleted <> 1" 
                + activeIds)))";

其中 activeIds = 一些数字的字符串,例如:1,2,3,4...

我想将此查询转换为存储过程。我的问题是将 activeIds 作为参数传递给此存储过程的最佳方法是什么?

*代码是 C# 并且使用的是 SQL Server 2008

【问题讨论】:

Table-Valued Parameters(不过,我不知道C#3是否知道SqlDbType.Structured @TimSchmelter - activeIds 只是一个字符串。我不能把它作为字符串传递吗? 不使用表值参数。为什么它需要是一个字符串?它不是来自某个集合吗?为什么将其转换为字符串只是必须将其拆分为非字符串?如果您要这样做,请继续使用您现在使用的 SQL 注入... 【参考方案1】:

是的,使用表值参数。首先创建一个类型:

CREATE TYPE dbo.StudentIDs(ID INT PRIMARY KEY);

现在您的程序可以使用它(请注意,我已将您的嵌套 IN 查询更改为正确的连接):

CREATE PROCEDURE dbo.MarkStudentsAsDeleted
  @IDs dbo.StudentIDs READONLY
AS
BEGIN
  SET NOCOUNT ON;

  UPDATE s SET IsDeleted = 1
    FROM dbo.Students AS s
    INNER JOIN dbo.Class AS c
    ON s.StudentId = c.StudentId
    INNER JOIN dbo.ClassValueTable AS ct
    ON c.PassId = ct.Id
    WHERE ct.IsDeleted <> 1
    AND EXISTS (SELECT 1 FROM @IDs WHERE StudentID = s.StudentID);
END 
GO

您的 C# 代码将传入一个 DataTable,或者您已经组装了 activeId 的集合,而不是一个逗号分隔的列表。

DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(int));
dt.Rows.Add(1);
dt.Rows.Add(2);
dt.Rows.Add(3);
dt.Rows.Add(4);

... open connection etc. ...

SqlCommand cmd = new SqlCommand("dbo.MarkStudentsAsDeleted", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvp1 = cmd.Parameters.AddWithValue("@IDs", dt);
tvp1.SqlDbType = SqlDbType.Structured;
cmd.ExecuteNonQuery();

... close, dispose, etc. ...

如果您想坚持将字符串传递给存储过程,则需要使用拆分函数。例如:

CREATE FUNCTION dbo.SplitInts
(
   @List       VARCHAR(MAX),
   @Delimiter  VARCHAR(255) = ','
)
RETURNS TABLE
WITH SCHEMABINDING 
AS
  RETURN 
  (  
    SELECT [value] = y.i.value('(./text())[1]', 'int')
    FROM 
    ( 
      SELECT x = CONVERT(XML, '<i>' 
        + REPLACE(@List, @Delimiter, '</i><i>') 
        + '</i>').query('.')
    ) AS a CROSS APPLY x.nodes('i') AS y(i)
  );
GO

现在你的存储过程可以是:

CREATE PROCEDURE dbo.MarkStudentsAsDeleted
  @IDs VARCHAR(MAX)
AS
BEGIN
  SET NOCOUNT ON;

  UPDATE s SET IsDeleted = 1
    FROM dbo.Students AS s
    INNER JOIN dbo.Class AS c
    ON s.StudentId = c.StudentId
    INNER JOIN dbo.ClassValueTable AS ct
    ON c.PassId = ct.Id
    WHERE ct.IsDeleted <> 1
    AND EXISTS (SELECT 1 FROM dbo.SplitInts(@IDs, ',') WHERE Item = s.StudentID);
END 
GO

【讨论】:

实际上我已经在“1,2,3,..”之类的字符串中有 activeIds。这就是为什么我要问是否仍然使用值参数或仅将其作为字符串传递给一个参数是否有意义?对不起,这个值参数的新手。非常感谢你的好例子!真的很感激! @NoviceMe 我明白了,但是这个逗号分隔的字符串来自是哪里来的?天空?你必须在某个时候以某种方式组装它...... 我从文件夹中获取列表并保存在代码中作为 IEnumerable 然后我使用 String.Join 制作逗号分隔列表。希望我现在能解释一下吗? 看,您正在获取一个集合,并将其转换为一个列表,只有这样 SQL Server 才能获取您的列表并将其转换回一个集合。你可以完全按照我在上面用IEnumerable&lt;SqlDataRecord&gt; 替换DataTable 所做的事情,现在你不必做任何String.Join 和废话。效率更高,代码更少。请不要试图将 JSON 思维强加到 SQL Server 上。 非常感谢您的解释,我会试试的!

以上是关于这里应该使用表值参数吗?的主要内容,如果未能解决你的问题,请参考以下文章

没有参数的内联表值函数?

在 SQL Server 的视图中使用表值函数

我可以在选择时让我的实体框架DbSet调用我的表值函数吗?

表值参数

使用 PetaPoco 将表值参数传递给存储过程

使用 ADO.NET 传递表值参数