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

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

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

SELECT * from Table WHERE ID IN(@IDs) 

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

那么我决定在Parameter集合中将Parameter(即@IDs)类型更改为string。 再试一次 – 仍然是相同的错误消息。

那么这种类型的数据集是否可以接受我的“1,2,3”参数? 目前我只有一些参数可以通过,所以我可以轻松地创建5个左右的参数并单独传递它们,但是如果有数百个呢? 有没有办法用逗号分隔参数调用Fill()方法?

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

您不能以这种方式将单个参数用于值列表。 但是可能有特定于数据库的方法来实现您想要的。 例如,使用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, ',')) 

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

在您的情况下,更改SQL –

原版的:

SELECT *来自表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, ”)

SQL Server 2008具有一个名为Table-Valued Parameters的function

所以你需要

  1. SELECT * from Table WHERE ID IN (SELECT * FROM (@IDs))将您的查询定义为SELECT * from Table WHERE ID IN (SELECT * FROM (@IDs))
  2. 返回Visual Studio中的TableAdapter可视化设计器,并更新@IDS参数以将@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或者例如使用Reflection来调整它。 以下是派生类的示例:

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

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

对于其他数据库,您必须构造SQL,或将字符串传递给数据库过程,该过程将其转换为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); } 

问候

您还可以创建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]}); } 

您还可以使用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:

  '''  ''' Converts an array of strings to XML values. '''  ''' Used to pass parameter values to the data store. 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("{0}", HttpUtility.HtmlEncode(value)) Next sb.AppendLine() sb.AppendFormat("", 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 

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

 List aList = new List(); 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();