Як передати параметри значення таблиці у збережену процедуру з коду .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.