124

我有一个MS SQL Server 2005数据库。在一些过程中,我将表参数传递给一个存储过程作为nvarchar(用逗号分隔)并在内部划分为单个值。我将它添加到SQL命令的参数列表如下:如何将表值参数传递给存储过程从.net代码

cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo"; 

我必须迁移数据库到SQL Server 2008。我知道有表值参数,我知道如何在存储过程中使用它们。但我不知道如何将其传递给SQL命令中的参数列表。 有没有人知道Parameters.Add程序的正确语法?或者有另一种方法来传递这个参数?

+0

看看这个解决方案:EF中的表值参数存储过程。 https://code.msdn.microsoft.com/Stored-Procedure-with-6c194514 – 2016-09-22 15:47:02

+0

在这种情况下,我通常连接字符串并将它们分离到服务器端,或者如果我有多个列,则传递甚至是xml。 Sql它处理XML时速度非常快。您可以尝试所有方法并检查处理时间,然后选择最佳方法。 XML看起来像 ...。 Sql Server上的过程也很简单。如果您需要更多信息,使用此方法,您总是可以向添加新属性。 – 2016-12-22 07:51:36

回答

209

DataTable,DbDataReaderIEnumerable<SqlDataRecord>对象可用于填充每个MSDN文章Table-Valued Parameters in SQL Server 2008 (ADO.NET)的表值参数。

下面的例子说明使用一个DataTableIEnumerable<SqlDataRecord>

SQL代码

CREATE TABLE dbo.PageView 
(
    PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED, 
    PageViewCount BIGINT NOT NULL 
); 
CREATE TYPE dbo.PageViewTableType AS TABLE 
(
    PageViewID BIGINT NOT NULL 
); 
CREATE PROCEDURE dbo.procMergePageView 
    @Display dbo.PageViewTableType READONLY 
AS 
BEGIN 
    MERGE INTO dbo.PageView AS T 
    USING @Display AS S 
    ON T.PageViewID = S.PageViewID 
    WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1 
    WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1); 
END 

C#代码

private static void ExecuteProcedure(bool useDataTable, 
            string connectionString, 
            IEnumerable<long> ids) 
{ 
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    { 
     connection.Open(); 
     using (SqlCommand command = connection.CreateCommand()) 
     { 
      command.CommandText = "dbo.procMergePageView"; 
      command.CommandType = CommandType.StoredProcedure; 

      SqlParameter parameter; 
      if (useDataTable) { 
       parameter = command.Parameters 
           .AddWithValue("@Display", CreateDataTable(ids)); 
      } 
      else 
      { 
       parameter = command.Parameters 
           .AddWithValue("@Display", CreateSqlDataRecords(ids)); 
      } 
      parameter.SqlDbType = SqlDbType.Structured; 
      parameter.TypeName = "dbo.PageViewTableType"; 

      command.ExecuteNonQuery(); 
     } 
    } 
} 

private static DataTable CreateDataTable(IEnumerable<long> ids) 
{ 
    DataTable table = new DataTable(); 
    table.Columns.Add("ID", typeof(long)); 
    foreach (long id in ids) 
    { 
     table.Rows.Add(id); 
    } 
    return table; 
} 

private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids) 
{ 
    SqlMetaData[] metaData = new SqlMetaData[1]; 
    metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt); 
    SqlDataRecord record = new SqlDataRecord(metaData); 
    foreach (long id in ids) 
    { 
     record.SetInt64(0, id); 
     yield return record; 
    } 
} 
+14

+1很好的例子。需要注意的是:发送'DataTable'作为参数值,将'SqlDbType'设置为'Structured'和'TypeName'作为数据库的UDT名称。 – 2013-01-25 09:52:29

+7

如果你打算在一个循环中重用一个引用类型的实例(在你的例子中是SqlDataRecord),请在这个特定的实例中添加一条评论,说明为什么它是安全的。 – 2016-01-21 11:42:23

+2

这段代码是错误的:空值表参数的值应该设置为null。如果给定一个空的'ids'参数''CreateSqlDataRecords'永远不会返回'null'。 – 2016-03-13 23:09:02

25

继瑞安的答案,你会还需要设置DataColumnOrdinal财产如果你正在处理table-valued parameter列其序号是不是按字母顺序。

举个例子,如果你有在SQL用作参数如下表值:

CREATE TYPE NodeFilter AS TABLE (
    ID int not null 
    Code nvarchar(10) not null, 
); 

您将需要订购在C#中的列这样:

table.Columns["ID"].SetOrdinal(0); 
// this also bumps Code to ordinal of 1 
// if you have more than 2 cols then you would need to set more ordinals 

如果你不这样做,你会得到一个解析错误,无法将nvarchar转换为int。

13

通用

public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector) 
    { 
     var tbl = new DataTable(); 
     tbl.Columns.Add("Id", typeof(T)); 

     foreach (var item in list) 
     { 
      tbl.Rows.Add(selector.Invoke(item)); 

     } 

     return tbl; 

    } 
+0

你能否让我知道我作为参数传递了什么? Func 选择器?不能仅仅是tbl.Rows.Add(item)而不需要该参数。 – GDroid 2015-03-18 20:59:31

+0

selector.Invoke(item)选择项目上的属性大多数情况下它的一个int,但它也允许你选择一个字符串属性 – Martea 2015-03-20 16:44:11

+0

你可以请提供一个例子,我如何把选择器放在那里?我有一个列表传递给存储过程... – GDroid 2015-03-23 03:31:28

5

与它合作最彻底的方法。假设你的表被称为“dbo.tvp_Int”整数列表(自定义为自己的表型)

创建此扩展方法...

public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data) 
{ 
    if(paramCollection != null) 
    { 
     var p = paramCollection.Add(parameterName, SqlDbType.Structured); 
     p.TypeName = "dbo.tvp_Int"; 
     DataTable _dt = new DataTable() {Columns = {"Value"}}; 
     data.ForEach(value => _dt.Rows.Add(value)); 
     p.Value = _dt; 
    } 
} 

现在你可以在一个添加表值参数只需通过这样做:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds); 
+1

如果paramCollection为NULL,该怎么办?如何传递空类型? – Muflix 2016-08-31 12:53:52

+2

@Muflix不明确的是,扩展方法实际上对空实例起作用。因此,在方法顶部添加一个简单的'if(paramCollection!= null)'检查将会很好 – Rhumborl 2017-03-16 21:41:09

+1

更新的答案与初始-if-检查 – 2017-03-25 02:53:35

相关问题