如何将表值参数从.net代码传递给存储过程

Mar*_*acz 162 c# sql-server stored-procedures sqlcommand table-valued-parameters

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

cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";
Run Code Online (Sandbox Code Playgroud)

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

Rya*_*hel 265

DataTable,DbDataReaderIEnumerable<SqlDataRecord>对象可用于根据SQL Server 2008(ADO.NET)中的MSDN文章表值参数填充表值参数.

以下示例说明使用a 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
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • +1优秀的例子.小贴士是:发送一个`DataTable`作为参数值,将`SqlDbType`设置为`Structured`和`TypeName`为数据库UDT名称. (23认同)
  • 如果要在循环中重用引用类型的实例(在您的示例中为SqlDataRecord),请在此特定实例中添加注释为何可以安全执行此操作. (10认同)
  • @Crono:`DataTable`(或`DataSet`)只实现它,因为它们必须支持Visual-Studio中的拖放功能,因此它们实现了实现`IDisposable`的`IComponent`.如果您不使用设计器但是手动创建它,则没有理由将其处理(或使用`using`语句).所以这是黄金法则"处置所有实现`IDisposable`"的例外之一. (4认同)
  • 此代码错误:空表值参数的值应设置为"null".如果给出一个空的`ids`参数,`CreateSqlDataRecords`永远不会返回`null`. (2认同)
  • @TimSchmelter作为一个经验法则,我总是称之为"Dispose"方法,即使只是因为如果我不这样做,代码分析也不会警告我.但我同意在使用基础`DataSet`和`DataTable`实例的特定场景中,调用`Dispose`不会做任何事情. (2认同)
  • @SørenBoisen:根据https://msdn.microsoft.com/en-us/library/microsoft.sqlserver.server.sqldatarecord.aspx的备注部分,"在编写公共语言运行时(CLR)应用程序时,你应该重新使用现有的SqlDataRecord对象,而不是每次都创建新对象." 这并不总是适用,但它可能适用于此. (2认同)
  • **未来用户**,请注意:`DataTable`方法消耗内存的地狱,而`IEnumerable`方法根本不消耗. (2认同)
  • @RuiTaborda 当您需要发送大量数据时,请使用“IEnumerable”方法。否则,随便用。 (2认同)

Sco*_*NET 30

继瑞安的回答,你也将需要设置DataColumnOrdinal,如果你正在处理一个属性table-valued parameter列,其序号是按字母顺序排列.

例如,如果您具有以下用作SQL中参数的表值:

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

您需要在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
Run Code Online (Sandbox Code Playgroud)

如果你没有这样做,你将得到一个解析错误,无法将nvarchar转换为int.


Mar*_*tea 14

通用

   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;

    }
Run Code Online (Sandbox Code Playgroud)


Sha*_*shi 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;
   }
}
Run Code Online (Sandbox Code Playgroud)

现在,您只需执行以下操作即可在任意位置添加一个表值参数:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds);
Run Code Online (Sandbox Code Playgroud)

  • @Muflix不明确地,扩展方法实际上对空实例起作用.因此,在方法的顶部添加一个简单的`if(paramCollection!= null)`检查就可以了 (2认同)
  • 也许有点迂腐,但我会在签名中使用`IEnumerable`而不是`List`,这样你就可以传递任何"IEnumerable",而不仅仅是列表,因为你没有使用任何特定于`List的函数`,我真的没有理由不给我们`IEnumerable` (2认同)