Jak przekazać parametry wartości tabeli do procedury składowanej z kodu .net


171

Mam bazę danych SQL Server 2005. W kilku procedurach mam parametry tabeli, które przekazuję do przechowywanego procesu jako nvarchar(oddzielone przecinkami) i wewnętrznie dzielę na pojedyncze wartości. Dodaję go do listy parametrów polecenia SQL w następujący sposób:

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

Muszę przeprowadzić migrację bazy danych do SQL Server 2008. Wiem, że istnieją parametry wartości tabeli i wiem, jak ich używać w procedurach składowanych. Ale nie wiem, jak przekazać je do listy parametrów w poleceniu SQL.

Czy ktoś zna poprawną składnię Parameters.Addprocedury? Czy jest inny sposób przekazania tego parametru?


Sprawdź to rozwiązanie: procedura składowana z parametrem z wartościami przechowywanymi w tabeli w EF. code.msdn.microsoft.com/Stored-Procedure-with-6c194514
— Carl Prothman

W takim przypadku zazwyczaj łączę ciągi znaków i dzielę je po stronie serwera lub przekazuję nawet plik XML, jeśli mam wiele kolumn. Sql jest bardzo szybki podczas przetwarzania XML. Możesz wypróbować wszystkie metody i sprawdzić czas przetwarzania, a następnie wybrać najlepszą metodę. XML wyglądałby następująco: <Items> <Item value = "sdadas" /> <Item value = "sadsad" /> ... </Items>. Proces na serwerze Sql jest również prosty. Korzystając z tej metody, zawsze możesz dodać nowy atrybut do <item>, jeśli potrzebujesz więcej informacji.
— Nițu Alexandru

4
@ NițuAlexandru, "Sql jest bardzo szybki podczas przetwarzania xml.". Nawet nie blisko.
— nothrow

Odpowiedzi:


279

DataTable, DbDataReaderlub IEnumerable<SqlDataRecord>obiektów można użyć do wypełnienia parametru wycenianego w tabeli zgodnie z artykułem MSDN Parametry z wartościami przechowywanymi w tabeli w programie SQL Server 2008 (ADO.NET) .

Poniższy przykład ilustruje użycie a DataTablelub an IEnumerable<SqlDataRecord>:

Kod 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

Kod 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 Doskonały przykład. Wnioski to: wyślij a DataTablejako wartość parametru, ustaw SqlDbTypena Structuredi TypeNamedo nazwy UDT bazy danych.
— lc.

10
Jeśli zamierzasz ponownie użyć instancji typu referencyjnego w pętli (w Twoim przykładzie SqlDataRecord), dodaj komentarz wyjaśniający, dlaczego jest to bezpieczne w tym konkretnym przypadku.
— Søren Boisen

2
Ten kod jest nieprawidłowy: parametry wartościowane w pustej tabeli powinny mieć ustawioną wartość null. CreateSqlDataRecordsnigdy nie zwróci, nulljeśli otrzyma pusty idsparametr.
— ta.speot.is

4
@Crono: DataTable(lub DataSet) implementują go tylko dlatego, że muszą obsługiwać możliwości przeciągania i upuszczania w Visual-Studio, więc implementują, IComponentktóre implementują IDisposable. Jeśli nie używasz projektanta, ale tworzysz go ręcznie, nie ma powodu, aby go pozbyć (lub użyć instrukcji using-statement). Jest to więc jeden z wyjątków od złotej zasady „pozbądź się wszystkiego, co wdraża IDisposable”.
— Tim Schmelter

2
@TimSchmelter Z reguły zawsze wywołuję Disposemetody, nawet jeśli jest to tylko po to, aby Analiza kodu nie ostrzegła mnie, jeśli tego nie zrobię. Ale zgadzam się, że w tym konkretnym scenariuszu, w którym używana jest baza DataSeti DataTableinstancje, wywołanie Disposenic nie da.
— Crono,

31

W nawiązaniu do odpowiedzi Ryana, będziesz musiał również ustawić właściwość DataColumn', Ordinaljeśli masz do czynienia table-valued parameterz wieloma kolumnami, których liczby porządkowe nie są w porządku alfabetycznym.

Na przykład, jeśli masz następującą wartość tabeli, która jest używana jako parametr w języku SQL:

CREATE TYPE NodeFilter AS TABLE (
  ID int not null
  Code nvarchar(10) not null,
);

Musisz uporządkować kolumny jako takie w 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

Jeśli tego nie zrobisz, pojawi się błąd analizy, nie udało się przekonwertować nvarchar na int.


15

Ogólny

   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;

    }

Czy mógłbyś dać mi znać, co mam podać jako parametr? Selektor Func <T, TProperty>? Czy nie może to być po prostu tbl.Rows.Add (pozycja) i nie potrzeba tego parametru.
— GDroid

the selector.Invoke (item) wybiera właściwość elementu w większości przypadków jest to int, ale pozwala również na wybranie właściwości ciągu
— Martea

czy możesz podać przykład, jak mam tam umieścić selektor? Mam listę <Guid> do przekazania do przechowywanego proc ...
— GDroid

guidList.ToTabledValuedParameter (x => x), ponieważ x jest guid w twoim przypadku, wynikiem będzie DataTable z jedną kolumną (id) z listą przewodników,
— Martea

5

Najczystszy sposób na pracę. Zakładając, że Twoja tabela to lista liczb całkowitych o nazwie „dbo.tvp_Int” (dostosuj do własnego typu tabeli)

Utwórz tę metodę rozszerzenia ...

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;
   }
}

Teraz możesz dodać parametr wartości tabeli w jednym wierszu w dowolnym miejscu, wykonując następujące czynności:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds);

1
co, jeśli paramCollection ma wartość NULL? Jak przekazać pusty typ?
— Muflix

2
@Muflix Co oczywiste, metody rozszerzające faktycznie działają na wystąpieniach o wartości null. Tak więc dodanie prostego if(paramCollection != null)czeku na górze metody będzie w porządku
— Rhumborl

1
Zaktualizowana odpowiedź z początkowym -if- sprawdzeniem
— Shahzad Qureshi

2
Może trochę pedantyczny, ale użyłbym IEnumerablezamiast Listw podpisie, w ten sposób możesz przekazać wszystko, co jest IEnumerable, nie tylko listy, Ponieważ nie używasz żadnej funkcji specyficznej dla List, naprawdę nie widzę powodu, aby tego nie robić nasIEnumerable
— Francis Lord

Korzystanie z listy pozwala na użycie skróconej metody data.ForEach (), w przeciwnym razie musiałbyś faktycznie napisać pętlę foreach. Co też może zadziałać, ale lubię pisać rzeczy tak krótkie, jak to tylko możliwe.
— Shahzad Qureshi

0

Użyj tego kodu, aby utworzyć odpowiedni parametr z Twojego typu:

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
    };
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.