Как передать параметр табличного значения
Я пытаюсь передать параметр табличного значения хранимой процедуре, но получаю исключение (см. Ниже).
SqlCommand c = new SqlCommand("getPermittedUsers", myConn) { CommandType = CommandType.StoredProcedure };
c.Parameters.AddWithValue("@intNotifyingUserId", notifyingUserId);
c.Parameters.AddWithValue("@tSelectedPdfIds", sharedPdfs).SqlDbType = SqlDbType.Structured;
SqlDataReader dr = c.ExecuteReader();
Тип определяется на сервере следующим образом:
CREATE TYPE [dbo].[IdList] AS TABLE(
[Id] [int] NOT NULL
)
Я попытался передать sharedPdfs как List<int>
, а также IQueryable<int>
, но продолжайте получать следующее исключение:
Object must implement IConvertible.
Кто-нибудь знает, что я делаю не так? Документация подразумевает, что я должен быть в состоянии передать список как TVP, но не приводит никаких примеров.
Спасибо.
2 ответа
Вы можете передать параметр как DataTable
, IEnumerable<SqlDataRecord>
, или же DbDataReader
,
В следующем примере показано использование 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;
}
}
Добавление нового ответа с обновленными ссылками.
Согласно документации (docs.microsoft.com) вы можете использовать один из следующих типов параметров:
SqlClient поддерживает заполнение возвращающих табличное значение параметров из объектов DataTable, DbDataReader или IEnumerable \ SqlDataRecord. Вы должны указать имя типа для возвращающего табличное значение параметра с помощью свойства TypeName объекта SqlParameter. Имя типа должно соответствовать имени совместимого типа, ранее созданного на сервере.
Не включен в документацию, но важен для высокопроизводительных приложений, пример с использованием IEnumerable
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using SqlConnection connection = this.GetConnection();
await connection.OpenAsync(timeout.Token);
using SqlCommand command = connection.CreateCommand();
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "Mycommand";
IEnumerable<SqlDataRecord> records = // ...
SqlParameter parameter = command.Parameters.Add("@MyObjects", SqlDbType.Structured);
parameter.TypeName = "MyCustomTableType";
parameter.Value = records;
await command.ExecuteNonQueryAsync(timeout.Token);
Пример использования DataTable:
// Create a DataTable with the modified rows.
DataTable addedCategories = CategoriesDataTable.GetChanges(DataRowState.Added);
// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand("usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("@tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
Пример использования DbDataReader:
// Assumes connection is an open SqlConnection.
// Retrieve data from Oracle.
OracleCommand selectCommand = new OracleCommand(
"Select CategoryID, CategoryName FROM Categories;",
oracleConnection);
OracleDataReader oracleReader = selectCommand.ExecuteReader(
CommandBehavior.CloseConnection);
// Configure the SqlCommand and table-valued parameter.
SqlCommand insertCommand = new SqlCommand(
"usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam =
insertCommand.Parameters.AddWithValue(
"@tvpNewCategories", oracleReader);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();