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


171

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

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

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

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


签出以下解决方案:EF中具有表值参数的存储过程。 code.msdn.microsoft.com/Stored-Procedure-with-6c194514
卡尔·普罗思曼

在这种情况下,我通常会串联字符串并在服务器端将其拆分,如果我有多个列,甚至会传递一个xml。SQL处理XML时非常快。您可以尝试所有方法并检查处理时间,然后选择最佳方法。XML看起来像<Items> <Item value =“ sdadas” /> <Item value =“ sadsad” />...</ Items>。Sql Server上的过程也很简单。如果需要更多信息,可以始终使用此方法向<item>添加新属性。
Nițu Alexandru

4
@ NițuAlexandru,“ Sql处理xml时非常快。” 差远了。
nothrow

Answers:


277

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

以下示例说明了使用DataTable还是IEnumerable<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;
    }
}

24
+1出色的例子。要点是:发送a DataTable作为参数值,设置SqlDbTypeStructuredTypeName到数据库UDT名称。
lc。

10
如果要在循环中重用引用类型的实例(在您的示例中为SqlDataRecord),请添加注释以说明为什么在此特定实例中这样做是安全的。
索伦Boisen

2
此代码是错误的:空表值参数的值应设置为null。如果给定一个空参数,CreateSqlDataRecords它将永远不会返回。nullids
ta.speot。是

4
@Crono :(DataTableDataSet)仅实现它,因为它们必须支持Visual Studio中的拖放功能,因此他们实现了IComponent哪些实现IDisposable。如果您不使用设计器而是手动创建它,则没有理由处置它(或使用using-statement)。因此,这是“配置所有实现的东西”黄金法则的例外之一IDisposable
蒂姆·施密特

2
@TimSchmelter作为经验法则,我总是调用Dispose方法,即使只是这样,如果我不这样做,代码分析也不会警告我。但是我同意,在使用基址DataSetDataTable实例的特定情况下,调用Dispose不会做任何事情。
克罗诺

31

除了Ryan的答案之外,如果您要处理包含多个序号按字母顺序排列的列,则还需要设置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。


15

泛型

   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 <T,TProperty>选择器?不能只是tbl.Rows.Add(item)而不需要该参数。
GDroid 2015年

selector.Invoke(item)在大多数情况下都会选择项目的属性,将其选择为int,但它也允许您选择字符串属性
Martea

您能提供一个示例说明如何将选择器放在那儿吗?我有一个List <Guid>传递给存储的过程...
GDroid

guidList.ToTabledValuedParameter(x => x),由于x在您的情况下是guid,因此返回的数据表将是一个带有id列表的column(id),
Martea 2015年

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 '16

2
@Muflix晦涩难懂的是,扩展方法实际上对空实例起作用。因此if(paramCollection != null),在该方法的顶部添加一个简单的检查会很好
Rhumborl

1
更新的答案带有初始-if-检查
Shahzad Qureshi

2
也许有点书呆子,但是我会在签名中使用它,IEnumerable而不是List在签名中使用,这样您就可以传递任何内容IEnumerable,而不仅仅是列表,因为您没有使用特定于的任何功能List,所以我真的没有理由不这样做我们IEnumerable
弗朗西斯·洛德

使用列表允许您使用速记数据.ForEach(),否则必须实际编写一个foreach循环。这也可以工作,但是我喜欢写得尽可能短。
Shahzad Qureshi

0

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

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
    };
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.