Як передати параметри значення таблиці у збережену процедуру з коду .net


171

У мене база даних SQL Server 2005. У кількох процедурах у мене є параметри таблиці, які я передаю до збереженого протоколу як nvarchar(розділений комами) і внутрішньо поділяю на одиничні значення. Я додаю його до списку параметрів команд SQL таким чином:

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

Мені потрібно перенести базу даних на SQL Server 2008. Я знаю, що є параметри значень таблиці, і я знаю, як їх використовувати в збережених процедурах. Але я не знаю, як передати його до списку параметрів у команді SQL.

Хтось знає правильний синтаксис Parameters.Addпроцедури? Або є інший спосіб передачі цього параметра?


Ознайомтеся з цим рішенням: Збережена процедура з табличним параметром у EF. code.msdn.microsoft.com/Stored-Procedure-with-6c194514
— Карл Протман

У такому випадку, як правило, я об'єдную рядок і розділяю їх на стороні сервера або передаю навіть xml, якщо у мене є кілька стовпців. Sql це дуже швидко при обробці xml. Ви можете спробувати всі методи і перевірити час обробки, а після цього вибрати найкращий метод. XML виглядатиме як <Items> <item value = "sdadas" /> <value value = "sadsad" /> ... </Items>. Процес на Sql Server також простий. Використовуючи цей метод, ви завжди можете додати новий атрибут до <item>, якщо вам потрібна додаткова інформація.
— Ніку Александру

4
@ NițuAlexandru, "Sql це дуже швидко при обробці xml." Навіть близько не.
— неросло

Відповіді:


279

DataTable, DbDataReaderабо IEnumerable<SqlDataRecord>об'єкти можуть бути використані для заповнення параметру з табличним значенням у статті MSDN Таблицево -оцінені параметри в SQL Server 2008 (ADO.NET) .

Наступний приклад ілюструє використання або a, 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 Відмінний приклад. Винос: надіслати DataTableяк параметр значення, встановлене SqlDbTypeна ім'я UDT бази даних Structuredта TypeNameна ім'я бази даних.
— лк.

10
Якщо ви збираєтеся повторно використовувати примірник типу посилання в циклі (SqlDataRecord у вашому прикладі), будь ласка, додайте коментар, чому це безпечно робити в цьому конкретному випадку.
— Søren Boisen

2
Цей код невірний: параметри, що оцінюються в порожній таблиці, повинні мати встановлене значення null. CreateSqlDataRecordsніколи не повернеться, nullякщо задати порожній idsпараметр.
— ta.speot.is

4
@Crono: DataTable(або DataSet) реалізувати його лише тому, що вони повинні підтримувати можливості перетягування та перетягування у Visual-Studio, щоб вони реалізували, IComponentякі реалізує IDisposable. Якщо ви не використовуєте дизайнер, але створюєте його вручну, немає причин розпоряджатися ним (або використовувати- usingзаяву). Тож це одне з винятків із золотого правила "розпоряджайтеся всім, що реалізує IDisposable".
— Тім Шмелтер

2
@TimSchmelter Як правило, я завжди викликаю Disposeметоди, навіть якщо це лише так, що аналіз коду не попередить мене, якщо я цього не роблю. Але я погоджуюся, що в цьому конкретному сценарії, де використовується база DataSetта DataTableекземпляри, виклик Disposeнічого не зробить.
— Кроно

31

В доповнення до відповіді Райана вам також потрібно встановити DataColumn«s Ordinalвластивість , якщо ви маєте справу з 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;

    }

Будь ласка, повідомте мені про те, що я передаю як параметр? Селектор функцій <T, TProperty>? Не може це бути просто tbl.Rows.Add (item) і не потрібно цього параметра.
— GDroid

the selector.Invoke (item) вибирає властивість для елемента, у більшості випадків його int, але він також дозволяє вибрати властивість string
— Martea

Чи можете ви надати приклад того, як я можу помістити селектор туди ?? У мене є список <Guid>, який потрібно передати до збережених процедур ...
— GDroid

guideList.ToTabledValuedParameter (x => x), оскільки x є орієнтиром у вашому випадку, повернення буде DataTable з одним стовпцем (ідентифікатором) із переліком
— настанов

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

2
@Muflix Незрозуміло, що методи розширення насправді працюють проти нульових випадків. Тож додавання простої if(paramCollection != null)перевірки у верхній частині методу буде добре
— Rhumborl

1
Оновлена ​​відповідь з початковою -if- чеком
— Шахзад Куреші

2
Можливо, трохи педантично, але я б використовував IEnumerableзамість Listпідпису, таким чином ви можете передавати все, що є IEnumerable, а не лише списки, оскільки ви не використовуєте жодну функцію, специфічну для List, я насправді не бачу причини не насIEnumerable
— Франциск Лорд

Використання списку дозволяє використовувати скорочені дані.ForEach (), інакше вам доведеться насправді написати цикл foreach. Що може також працювати, але мені подобається писати речі якомога коротше.
— Шахзад Куреші

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
    };
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.