我有一个 SQL Server 2005 数据库。在一些过程中,我将表参数作为 nvarchar
(用逗号分隔)传递给存储过程,并在内部分为单个值。我将它添加到 SQL 命令参数列表中,如下所示:
cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";
我必须将数据库迁移到SQL Server 2008。我知道有表值参数,并且我知道如何在存储过程中使用它们。但我不知道如何将一个传递给 SQL 命令中的参数列表。
有人知道 Parameters.Add
过程的正确语法吗?还是有另一种方法来传递这个参数?
DataTable
、DbDataReader
或 IEnumerable<SqlDataRecord>
对象可用于根据 MSDN 文章 Table-Valued Parameters in 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;
}
}
根据 Ryan 的回答,如果您正在处理具有 multiple 列的 table-valued parameter
,其序数为 not,则您还需要设置 DataColumn
的 Ordinal
属性按字母顺序。
例如,如果您将下表值用作 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。
通用的
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;
}
使用它的最干净的方式。假设您的表是一个名为“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);
if(paramCollection != null)
检查就可以了
IEnumerable
而不是 List
,这样您就可以传递 IEnumerable
的任何内容,而不仅仅是列表,因为您没有使用任何特定于 {2 的函数},我真的没有理由不接受我们IEnumerable
使用此代码从您的类型创建合适的参数:
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
};
}
DataTable
作为参数值,将SqlDbType
设置为Structured
并将TypeName
设置为数据库 UDT 名称。null
。如果给定一个空的ids
参数,CreateSqlDataRecords
将永远不会返回null
。DataTable
(或DataSet
)仅实现它,因为它们必须支持 Visual-Studio 中的拖放功能,因此它们实现了实现IDisposable
的IComponent
。如果您不使用设计器而是手动创建它,则没有理由丢弃它(或使用using
语句)。所以这是黄金法则“处置所有实现IDisposable
”的例外之一。DataTable
方法会消耗大量内存,而IEnumerable
则不会。