ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我必须将数据库迁移到SQL Server 2008。我知道有表值参数,并且我知道如何在存储过程中使用它们。但我不知道如何将一个传递给 SQL 命令中的参数列表。

有人知道 Parameters.Add 过程的正确语法吗?还是有另一种方法来传递这个参数?

查看此解决方案:EF 中具有表值参数的存储过程。 code.msdn.microsoft.com/Stored-Procedure-with-6c194514
在这种情况下,我通常连接字符串并在服务器端拆分它们,或者如果我有多个列,甚至传递一个 xml。 Sql 在处理 xml 时非常快。您可以尝试所有方法并检查处理时间,然后选择最佳方法。 XML 看起来像 ...。 Sql Server 上的过程也很简单。使用此方法,如果您需要更多信息,您可以随时向 添加新属性。
@NişuAlexandru,“处理 xml 时,Sql 非常快。”。差远了。

K
Kjartan

DataTableDbDataReaderIEnumerable<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;
    }
}

+1 很好的例子。要点是:发送一个 DataTable 作为参数值,将 SqlDbType 设置为 Structured 并将 TypeName 设置为数据库 UDT 名称。
如果您要在循环中重用引用类型的实例(在您的示例中为 SqlDataRecord),请添加评论,说明为什么在此特定实例中这样做是安全的。
此代码错误:空表值参数应将其值设置为 null。如果给定一个空的 ids 参数,CreateSqlDataRecords 将永远不会返回 null
@Crono:DataTable(或 DataSet)仅实现它,因为它们必须支持 Visual-Studio 中的拖放功能,因此它们实现了实现 IDisposableIComponent。如果您不使用设计器而是手动创建它,则没有理由丢弃它(或使用 using 语句)。所以这是黄金法则“处置所有实现 IDisposable”的例外之一。
未来的用户,请注意这一点:DataTable 方法会消耗大量内存,而 IEnumerable 则不会。
K
KyleMit

根据 Ryan 的回答,如果您正在处理具有 multiple 列的 table-valued parameter,其序数为 not,则您还需要设置 DataColumnOrdinal 属性按字母顺序。

例如,如果您将下表值用作 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。


M
Martea

通用的

   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;

    }

请让我知道我将什么作为参数传递? Func 选择器?难道不是简单的 tbl.Rows.Add(item) 并且不需要那个参数。
selector.Invoke(item) 选择项目上的属性,大多数情况下它是一个 int,但它也允许您选择一个字符串属性
你能提供一个我如何把选择器放在那里的例子吗?我有一个 List 要传递给存储过程...
guidList.ToTabledValuedParameter(x=>x),因为 x 是您的情况下的 guid,所以返回将是一个 DataTable,其中有一列(id)和一个 guid 列表,
S
Shahzad Qureshi

使用它的最干净的方式。假设您的表是一个名为“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);

如果 paramCollection 为 NULL 怎么办?如何传递空类型?
@Muflix 晦涩难懂,扩展方法实际上适用于空实例。所以在方法顶部添加一个简单的 if(paramCollection != null) 检查就可以了
使用初始 -if- 检查更新答案
也许有点迂腐,但我会在签名中使用 IEnumerable 而不是 List,这样您就可以传递 IEnumerable 的任何内容,而不仅仅是列表,因为您没有使用任何特定于 {2 的函数},我真的没有理由不接受我们IEnumerable
使用 List 允许您使用速记 data.ForEach(),否则您必须实际编写一个 foreach 循环。这也可以,但我喜欢写尽可能短的东西。
b
bside

使用此代码从您的类型创建合适的参数:

private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
    DataTable dt = new DataTable();

    var properties = typedParameter.GetType().GetProperties().ToList();
    properties.ForEach(p =>
    {
        dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
    });
    var row = dt.NewRow();
    properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
    dt.Rows.Add(row);

    return new SqlParameter
    {
        Direction = ParameterDirection.Input,
        ParameterName = name,
        Value = dt,
        SqlDbType = SqlDbType.Structured
    };
}