使用 .NET 中的类型化数据集将 SQL 参数传递给 IN() 子句

Posted

技术标签:

【中文标题】使用 .NET 中的类型化数据集将 SQL 参数传递给 IN() 子句【英文标题】:Passing a SQL parameter to an IN() clause using typed datasets in .NET 【发布时间】:2011-07-21 01:46:17 【问题描述】:

首先道歉,因为这个网站上有类似的问题,但他们都没有直接回答这个问题。

我在 VS 2010 中使用类型化数据集。我在 Dataset 中创建了一个 TableAdapter,查询如下:

SELECT * from Table WHERE ID IN(@IDs)

现在,如果我调用:TableAdapter.Fill(MyDataTable,"1,2,3") 会发生错误,指出 VS 无法将 1、2、3 转换为 int 类型。很公平。

然后我决定将参数(即@IDs)类型更改为参数集合中的字符串。再试一次 - 仍然是同样的错误消息。

那么这个类型化的数据集有什么方法可以接受我的“1,2,3”参数吗?目前我只有几个参数要传递,所以我可以轻松地创建 5 个左右的参数并分别传递它们,但是如果有数百个呢?有什么方法可以用逗号分隔的参数调用Fill() 方法吗?

(我知道我可以使用动态 SQL 来创建语句并执行它,但如果有另一种方法允许我保留我的类型化数据集以用于例如 ReportViewer/bindingsources)

【问题讨论】:

是 SQL Server 吗?如果是,哪个版本? SQL server 2008(及更高版本)允许表值参数。但这需要 SQL 服务器上的 TYPE 定义。如果允许您在服务器上使用创建 TYPE,我可以回答这个问题。 是的,我有完全访问权限,请详细说明。 对于阅读本文的任何人,请查看 Joe 和 Simon M 的答案。他们都帮助了我的问题 这可能会有所帮助:***.com/questions/570393/… 【参考方案1】:

您不能以这种方式将单个参数用于值列表。但是可能有特定于数据库的方法来实现您想要的。例如,使用 SQL Server 2005 或更高版本,您可以创建一个表值函数来拆分字符串参数,例如:

CREATE FUNCTION dbo.F_Split
(
@InputString VARCHAR(MAX)
,@Separator VARCHAR(MAX)
)
RETURNS @ValueTable TABLE (Value VARCHAR(MAX))
AS
BEGIN

    DECLARE @SeparatorIndex INT, @TotalLength INT, @StartIndex INT, @Value VARCHAR(MAX)
    SET @TotalLength=LEN(@InputString)
    SET @StartIndex = 1

    IF @Separator IS NULL RETURN

    WHILE @StartIndex <= @TotalLength
    BEGIN
        SET @SeparatorIndex = CHARINDEX(@Separator, @InputString, @StartIndex)
        IF @SeparatorIndex > 0
        BEGIN
            SET @Value = SUBSTRING(@InputString, @StartIndex, @SeparatorIndex-@StartIndex)
            SET @StartIndex = @SeparatorIndex + 1
        END
        ELSE
        BEGIN
            Set @Value = SUBSTRING(@InputString, @StartIndex, @TotalLength-@StartIndex+1)
            SET @StartIndex = @TotalLength+1
        END
        INSERT INTO @ValueTable
        (Value)
        VALUES
        (@Value)
    END

    RETURN
END

然后您将按如下方式使用它:

SELECT * from Table WHERE ID IN (SELECT CAST(Value AS INT) FROM F_Split(@IDs, ','))

【讨论】:

效果很好。我只想添加一件事来说明清楚。 @IDs 必须像查询中的“1,2,3”。当我尝试它时我很困惑。 但是如何在类型化数据集中声明函数?【参考方案2】:

我尝试了一种在 SQL 方式中使用字符串“包含”概念的解决方法:

在你的情况下,改变 SQL -

原文:

SELECT * from Table WHERE ID IN(@IDs)

成为:

SELECT * from Table WHERE CharIndex(','+Cast(ID As Varchar(10))+',',@IDs) > 0

使用 .net 代码 -

原文:

TableAdapter.Fill(MyDataTable,"1,2,3")

成为:

TableAdapter.Fill(MyDataTable,",1,2,3,")

【讨论】:

太棒了!完美运行。 完美运行!它甚至相应地改变了参数类型【参考方案3】:

SQL Server 2008 有一个名为Table-Valued Parameters的功能

所以你需要

    将您的查询定义为SELECT * from Table WHERE ID IN (SELECT * FROM (@IDs)) 返回 Visual Studio 中的 TableAdapter 可视化设计器,并更新 @IDS 参数以将 @IDS 参数修改为 DbType=Object 和 ProviderType=Structured 在您正在使用的数据库中运行此 SQL 批处理:CREATE TYPE MyIntArray AS TABLE ( Value INT );GO。 这将创建一个 MyIntArray “表类型”,其中只有一列 INT 类型。 现在棘手的事情是将“MyIntArray”类型传递给 ADO.NET 端的 TableAdapter。

很遗憾 Table Adapter 设计器不支持SqlParameter.TypeName 参数,所以我们需要自己修复它。目标是修改生成的 TableAdapter 类的CommandCollection 属性。不幸的是,这个属性是受保护的,所以你必须派生 TableAdapter 或者例如使用反射来调整它。下面是一个派生类的例子:

    public class MyTableAdapter2 : MyTableAdapter
    
        public MyTableAdapter2()
        
            SqlCommand[] cmds = base.CommandCollection;
            // here, the IDS parameter is index 0 of command 1
            // you'll have to be more clever, but you get the idea
            cmds[1].Parameters[0].TypeName = "MyIntArray";
        
    

你可以这样调用这个方法:

        MyTableAdapter t = new MyTableAdapter2();

        // create the TVP parameter, with one column. the name is irrelevant.
        DataTable tvp = new DataTable();
        tvp.Columns.Add();

        // add one row for each value
        DataRow row = tvp.NewRow();
        row[0] = 1;
        tvp.Rows.Add(row);

        row = tvp.NewRow();
        row[0] = 2;
        tvp.Rows.Add(row);

        row = tvp.NewRow();
        row[0] = 3;
        tvp.Rows.Add(row);

        t.Fill(new MyDataTable(), tvp);

【讨论】:

这是我能用 TableAdapters 找到的唯一一个简单的 1 TVP 行示例。效果很好!谢谢! 您还需要为“GRANT EXECUTE ON TYPE ::MyIntArray to user; GO”类型添加执行权限 ID 周围的括号很可能是多余的【参考方案4】:

我可以通过将ClearBeforeFill 属性设置为false 并在foreach 循环中填充TableAdapter 来解决此问题。

List<string> aList = new List<string>();
aList.Add("1");
aList.Add("2");
aList.Add("3");

yourTableAdapter.ClearBeforeFill = true;
yourTableAdapter.Fill(yourDataSet.yourTableName, ""); //clears table

foreach (string a in aList)

    yourTableAdapter.ClearBeforeFill = false;
    yourTableAdapter.Fill(yourDataSet.yourTableName, a);

yourTableAdapter.Dispose();

【讨论】:

【参考方案5】:

我所知道的唯一可以在IN 子句中使用.NET 参数的数据库是PostgreSQL,因为PostgreSQL 有一个可以与IN 一起使用的数组概念,并且Npgsql 允许数组(或IEnumerable&lt;T&gt;)参数。

对于其他数据库,您必须构建 SQL,或者将字符串传递给数据库过程,将其转换为 0 个或多个参数,然后对其进行操作。

【讨论】:

【参考方案6】:

@Joe 是对的。

或者您可以使用 foreach 循环来执行此操作。

类似:

   int[] arr = new int[3]; 
    arr[0] = "1";        
    arr[1] = "2";             
    arr[2] = "3";             

foreach(vat data in arr)


//Do your Code here

// 
var MyDatatable = obj.GetDatabyID(data);

TableAdapter.Fill(MyDataTable);


问候

【讨论】:

【参考方案7】:

您还可以创建 ID 参数列表 因此,您将使用@ID1、@ID2、@ID3 等代替@IDs

var sql = "SELECT * from Table WHERE ID IN (" + getKeys(values.Count) + ")";

然后 getKeys(count) 做这样的事情:

var result = string.Empty;
            for (int i = 0; i < count; i++)
            
                result += ", @ID" + i;
            
            return string.IsNullOrEmpty(result) ? string.Empty : result.Substring(1);

最后,添加参数:

foreach (int i = 0; i < values.Count; i++)
            
                cmd.Parameters.Add(new SqlParameter("@ID" + i, SqlDbType.VarChar)  Value = values[i]);
            

【讨论】:

【参考方案8】:

您还可以使用 XML 将参数列表传入存储过程:

1) 在 Visual Studio 中:

创建一个新的 Tableadapter 并创建一个 Typed Dataset 以获取单个记录:

SELECT * FROM myTable WHERE (ID = @ID)

2) 在 SQL Server 管理器中:

使用与您的类型化数据集相同的选择字段创建一个存储过程:

CREATE PROCEDURE [dbo].[usrsp_GetIds]
    @paramList xml = NULL
AS
    SET NOCOUNT ON;

/*
Create a temp table to hold paramaters list.
Parse XML string and insert each value into table.
Param list contains: List of ID's
*/
DECLARE @tblParams AS TABLE (ID INT)
INSERT INTO @tblParams(ID) 
    SELECT 
        XmlValues.ID.value('.', 'INT')
    FROM 
        @paramList.nodes('/params/value') AS XmlValues(ID)

/* 
Select records that match ID's in param list:
*/
SELECT * FROM myTable 
WHERE 
    ID IN (
        SELECT ID FROM @tblParams
    )

3) 在 Visual Studio 中:

向您的 Tableadapter 添加一个新查询,选择上面创建的存储过程 usrsp_GetIds 并将其命名为 FillBy_Ids。这将创建命令:

TableAdapter.FillBy_Ids(@paramList)

4) 在 Visual Studio 中:

在您的 .net 代码中创建一个实用函数来将字符串数组转换为 XML:

    ''' <summary>
    ''' Converts an array of strings to XML values.
    ''' </summary>
    ''' <remarks>Used to pass parameter values to the data store.</remarks>
    Public Shared Function ConvertToXML(xmlRootName As String, values() As String) As String
        'Note: XML values must be html encoded.
        Dim sb As New StringBuilder
        sb.AppendFormat("<0>", HttpUtility.HtmlEncode(xmlRootName))
        For Each value As String In values
            sb.AppendLine()
            sb.Append(vbTab)
            sb.AppendFormat("<value>0</value>", HttpUtility.HtmlEncode(value))
        Next
        sb.AppendLine()
        sb.AppendFormat("</0>", xmlRootName)
        Return sb.ToString
    End Function

用法示例:

通过将字符串列表作为参数传递,使用强类型函数填充您的数据表:

'Create a list of record IDs to retrieve:
Dim ids as New List(of String)
ids.Add(1)
ids.Add(2)
ids.Add(3)

'Convert the list of IDs to an XML string:
Dim paramsXml As String = ConvertToXML("params", ids.ToArray)

'Get the records using standard DataTable & TableAdapter methods:
Using myDT As New MyDataTable
    Using myTA As New MyTableAdapter

        myTA.FillBy_Ids(myDT, paramsXml)

        For Each row In myDT
            'do stuff:
        Next

    End Using
End Using

【讨论】:

以上是关于使用 .NET 中的类型化数据集将 SQL 参数传递给 IN() 子句的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.Net Core 项目中使用 ADO.Net 将 JSON 类型作为参数传递给 SQL Server 2016 存储过程

odp.net 可以将参数传递给布尔 pl/sql 参数吗?

.Net防sql注入的方法总结

如何将 XML 数据作为参数传递给从 C# 到 Sql 服务器的 TVP 参数

下拉列表将参数传递给 vb.net 中的 webmethod

参数化sql查询语句