Как передать параметры значения таблицы в хранимую процедуру из .net-кода
У меня есть база данных MS SQL Server 2005. В нескольких процедурах у меня есть параметры таблицы, которые я передаю в сохраненный proc как nvarchar (разделенный запятыми) и внутренне делятся на отдельные значения. Я добавляю его в список параметров команды SQL следующим образом:
cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";
Мне нужно перенести базу данных на SQL Server 2008. Я знаю, что есть параметры значения таблицы, и я знаю, как их использовать в хранимых процедурах. Но я не знаю, как передать его в список параметров в команде SQL.
Кто-нибудь знает правильный синтаксис процедуры Parameters.Add
? Или есть другой способ передать этот параметр?
Ответы
Ответ 1
Объекты DataTable
, DbDataReader
или IEnumerable<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
Код С#:
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;
}
}
Ответ 2
В ответ на ответ Ryan вам также нужно будет установить свойство DataColumn
Ordinal
, если вы имеете дело с table-valued parameter
с столбцами multiple, чьи ординалы не в алфавитном порядке.
В качестве примера, если у вас есть следующее значение таблицы, которое используется как параметр в SQL:
CREATE TYPE NodeFilter AS TABLE (
ID int not null
Code nvarchar(10) not null,
);
Вам нужно будет заказать свои столбцы как таковые в С#:
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.
Ответ 3
Общий
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;
}
Ответ 4
Самый чистый способ работать с ним. Предполагая, что ваша таблица представляет собой список целых чисел, называемых "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);
Ответ 5
Используйте этот код для создания параметра из вашего типа:
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, p.PropertyType); });
var row = dt.NewRow();
properties.ForEach(p => { row[p.Name] = p.GetValue(typedParameter); });
dt.Rows.Add(row);
return new SqlParameter
{
Direction = ParameterDirection.Input,
ParameterName = name,
Value = dt,
SqlDbType = SqlDbType.Structured
};
}
Ответ 6
Используйте этот код для создания подходящего параметра из вашего типа:
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
};
}