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

15

首先向大家道歉,因为这个网站上有类似的问题,但是没有一个能够直接回答这个问题。

我在使用VS 2010中的类型化数据集。我创建了一个包含查询的数据集中的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吗?如果是,它的版本号是多少? - Simon Mourier
SQL Server 2008(及更高版本)允许使用表值参数。但这需要在 SQL Server 上进行 TYPE 定义。如果您被允许在服务器上创建一个 TYPE,我可以回答这个问题。 - Simon Mourier
是的,我有完全的访问权限,请详细说明。 - Simon
对于任何阅读此内容的人,请查看Joe和Simon M的答案。他们都帮助了我的问题。 - Simon
这可能会有所帮助:https://dev59.com/inRB5IYBdhLWcg3wn4YL#7733714 - RhinoDevel
9个回答

10

以这种方式无法使用单个参数来代表值列表。但是可能有特定于数据库的方法可以实现您想要的功能。例如,对于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”这样的形式。当我尝试时感到困惑。 - Hoorayo
但是在类型化数据集中,你如何声明函数? - raberana

9
我尝试了一种解决方法,用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,")

太棒了!完美运作。 - Jim Simson
完美运行!它甚至相应地更改了参数类型。 - Rudy Hinojosa

6

SQL Server 2008有一个名为表值参数的功能。

所以你需要:

  1. 将查询定义为SELECT * from Table WHERE ID IN (SELECT * FROM (@IDs))
  2. 回到Visual Studio中的TableAdapter可视化设计器,并将@IDS参数更新为DbType=Object和ProviderType=Structured。
  3. 在使用的数据库中运行此SQL批处理:CREATE TYPE MyIntArray AS TABLE ( Value INT );GO。这将创建一个只有一列INT类型的MyIntArray“表类型”。
  4. 现在棘手的事情是将“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行示例。非常好用!谢谢! - johntrepreneur
您还需要为类型“GRANT EXECUTE ON TYPE ::MyIntArray to user; GO”添加执行权限。 - sweetfa
很可能ID周围的括号是多余的。 - sweetfa

2

我通过将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();

1
我所知道的唯一一个可以使用.NET参数在IN子句中的数据库是PostgreSQL,因为PostgreSQL有一个可以与IN一起使用的数组概念,并且Npgsql允许数组(或IEnumerable<T>)参数。
对于其他数据库,您必须构造SQL或将字符串传递给数据库过程,然后将其转换为0个或多个参数并对其进行操作。

0
您还可以创建一个ID参数列表,这样就不需要使用@IDs,而是使用@ID1、@ID2、@ID3等。
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]});
            }

0

@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);

}

敬礼


0

你也可以使用XML将参数列表传递给存储过程:

1)在Visual Studio中:

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

SELECT * FROM myTable WHERE (ID = @ID)

2) 在SQL Server Manager中:

创建一个存储过程,其选择的字段与您键入的数据集相同:

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

0

哟呼,它成功了!!!

由于不同的搜索元素都是具有预定义头部(“2021”)的固定长度订单号,因此我甚至没有放置分隔符。

MySQL:

[...] WHERE
    (INSTR(@orders, CAST(orders.numorder AS CHAR (11))) > 0)
[...]

C#:

 string allOrders="";
    foreach (string orderNum in ordersNum)
    {
         allOrders += orderNum;
         textBoxOrdersNum.AppendText ( orderNum+"\r\n");
    }
tousDetailsNomenclatureParOrderTableAdapter.FillTousDetailsNomenclatureParOrder(conduiteDataSet.TousDetailsNomenclatureParOrder, allOrders);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接