使用 .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<T>
)参数。
对于其他数据库,您必须构建 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 参数吗?
如何将 XML 数据作为参数传递给从 C# 到 Sql 服务器的 TVP 参数