#c# #sql-server #sqlcommand #sqlexception #table-valued-parameters
#c# #sql-сервер #sqlcommand #sqlexception #параметры с табличным значением
Вопрос:
В C # я пытаюсь передать DataTable в качестве параметра в оператор SQL. Приведенный ниже код:
protected virtual void DoDeleteRecords(List<Guid> ids)
{
if (ids.Count > 0)
{
DataTable tvp = new DataTable();
tvp.Columns.Add("Id", typeof(Guid));
foreach (Guid id in ids)
{
DataRow row = tvp.NewRow();
row["Id"] = id;
tvp.Rows.Add(row);
}
string sql = string.Format("DELETE FROM MyTable WHERE ID IN ({0})", "@IDTable");
SqlConnection connection = new SqlConnection(CoreSettings.ConnectionString);
using (connection)
{
SqlCommand command = new SqlCommand(sql, connection);
SqlParameter tvpParam = command.Parameters.AddWithValue("@IDTable", tvp);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.IDList";
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
}
}
Однако, когда команда.Вызывается ExecuteNonQuery, я получаю следующую ошибку SQLException:
Необходимо объявить скалярную переменную «@IDTable»
Я понимаю, что эта ошибка обычно связана с пропуском значения параметра, но, насколько я могу судить, у меня это есть.
Кто-нибудь может увидеть, что я делаю не так?
Большое спасибо.
ОБНОВЛЕНИЕ Я изменил вопрос, чтобы удалить дерьмовый код, обогащенный SQL-инъекциями, из моего примера.
Комментарии:
1. Вы не можете передавать TVP в специальных операторах. Таким образом поддерживаются только параметры хранимых процедур. Рассмотрите возможность использования Dapper; это неинтересный шаблон, а Dapper поддерживает построение списков.
2. Аааа, хорошо. Ну, тогда это все объясняет! Спасибо @JeroenMostert
3. Что такое
"DELETE FROM " tableName " WHERE...
«попытка достичь, когдаtableName
это TVP? Это привело бы к введению скалярного значения в предложение. Кроме того, этот тип синтаксиса является ужасающей уязвимостью в системе безопасности. Сейчас 2021 год, внедрение SQL должно было прекратиться много лет назад.4. @JeroenMostert Это мусор, конечно, вы можете. Но TVP не используется, OP просто объединяет список прямо в запрос. Правильный код
"DELETE FROM " tableName " WHERE " idColumnName " IN (SELECT * FROM @IDTable)"
неstring.Format
требуется. В любом случае этот код выглядит довольно опасным, полностью открытым для SQL-инъекции5. @Charlieface вы абсолютно правы. Я пропустил ВЫБОР из IN. Черт возьми! Спасибо, что заметили это. И просто для ясности, это не производственный код — просто то, что я использую для проверки некоторых идей. И нет, одна из этих идей не «как максимально использовать мой SQL»!
Ответ №1:
Перво-наперво: я понятия не имею, откуда вы получаете tableName
and columnName
, но если они предоставлены пользователем, то это открыто для SQL-инъекции. По крайней мере, используйте QUOTENAME()
, чтобы гарантировать, что фактический код не вводится.
Во-вторых, вы фактически не используете TVP. В имеющемся у вас коде просто говорится IN (@IDTable)
, что это не то, как вы используете TVP.
TVP — это просто табличная переменная, и ее следует использовать как любую другую таблицу:
protected virtual void DoDeleteRecords(List<Guid> ids)
{
if (ids.Count == 0)
return;
DataTable tvp = new DataTable();
tvp.Columns.Add("Id", typeof(Guid));
foreach (Guid id in ids)
tvp.Rows.Add(id);
const string sql = @"
DELETE FROM table
WHERE idColumnName IN (SELECT * FROM @IDTable);
";
using(SqlConnection connection = new SqlConnection(CoreSettings.ConnectionString))
using(SqlCommand command = new SqlCommand(sql, connection))
{
command.Parameters.Add(
new SqlParameter("@IDTable", SqlDbType.Structured)
{
Value = tvp,
Direction = ParameterDirection.Input,
TypeName = "dbo.IDList"
});
connection.Open();
command.ExecuteNonQuery();
}
}
Комментарии:
1. Вы совершенно правы, исходный код, который я опубликовал, был дерьмом, и теперь я исправил свой вопрос, чтобы жестко закодировать имена таблиц / столбцов в инструкции SQL. Не было никакой реальной причины для их объединения в качестве переменных. Кроме того, спасибо, что заметили мою ошибку с таблицей TVP. Настоящий момент с ладонью!
2. Я полагаю, вам также нужно
ParameterDirection.Input
(эквивалентREADONLY
в SQL), добавили