Come passare i parametri del valore della tabella alla stored procedure dal codice .net


171

Ho un database di SQL Server 2005. In alcune procedure ho parametri di tabella che passo a un proc memorizzato come nvarchar(separato da virgole) e diviso internamente in valori singoli. Lo aggiungo all'elenco dei parametri del comando SQL in questo modo:

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

Devo migrare il database su SQL Server 2008. So che ci sono parametri di valori di tabella e so come usarli nelle stored procedure. Ma non so come passarne uno all'elenco dei parametri in un comando SQL.

Qualcuno conosce la sintassi corretta della Parameters.Addprocedura? O c'è un altro modo per passare questo parametro?


Dai un'occhiata a questa soluzione: Stored procedure con parametro con valori di tabella in EF. code.msdn.microsoft.com/Stored-Procedure-with-6c194514
Carl Prothman

In un caso come questo, di solito concateno le stringhe e le divido sul lato server o passo anche un XML se ho più colonne. SQL è molto veloce durante l'elaborazione di XML. Puoi provare tutti i metodi e controllare i tempi di elaborazione e successivamente scegliere il metodo migliore. Un XML sarebbe simile a <Items> <Item value = "sdadas" /> <Item value = "sadsad" /> ... </Items>. Anche il processo su SQL Server è semplice. Utilizzando questo metodo, puoi sempre aggiungere un nuovo attributo a <item> se hai bisogno di maggiori informazioni.
Nițu Alexandru,

4
@ NițuAlexandru, "Sql è molto veloce durante l'elaborazione di XML.". Neanche vicino.
nothrow

Risposte:


279

DataTable, DbDataReadero IEnumerable<SqlDataRecord>oggetti possono essere utilizzati per popolare un parametro con valori di tabella per l'articolo MSDN Parametri con valori di tabella in SQL Server 2008 (ADO.NET) .

L'esempio seguente mostra l'uso di a DataTableo anIEnumerable<SqlDataRecord> :

Codice 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

Codice 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 Esempio eccellente. I takeaway sono: invia a DataTablecome valore del parametro, imposta SqlDbTypesu Structurede TypeNamesul nome UDT del database.
lc.

10
Se hai intenzione di riutilizzare un'istanza di un tipo di riferimento in un ciclo (SqlDataRecord nel tuo esempio), ti preghiamo di aggiungere un commento sul perché è sicuro farlo in questa particolare istanza.
Søren Boisen,

2
Questo codice è errato: i parametri con valori di tabella vuoti devono avere il valore impostato su null. CreateSqlDataRecordsnon ritornerà mai nullse viene dato un idsparametro vuoto .
ta.speot.is

4
@Crono: DataTable(o DataSet) implementarlo solo perché devono supportare le funzionalità di trascinamento della selezione in Visual-Studio, quindi implementano IComponentquali implementano IDisposable. Se non si utilizza il designer ma lo si crea manualmente, non c'è motivo di eliminarlo (o utilizzare lo usingstato). Quindi questa è una delle eccezioni della regola d'oro "disporre tutto ciò che implementa IDisposable".
Tim Schmelter,

2
@TimSchmelter Come regola generale, chiamo sempre Disposemetodi, anche se è solo per fare in modo che Code Analysis non mi avverta se non lo faccio. Ma sono d'accordo che in questo specifico scenario in cui vengono utilizzate la base DataSete le DataTableistanze, la chiamata Disposenon farebbe nulla.
Crono,

31

A seguito della risposta di Ryan sarà necessario anche impostare la DataColumn's Ordinalproprietà se avete a che fare con un table-valued parametercon più colonne i cui numeri ordinali sono non in ordine alfabetico.

Ad esempio, se si dispone del seguente valore di tabella utilizzato come parametro in SQL:

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

Dovresti ordinare le tue colonne come tali in 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

Se non riesci a farlo, otterrai un errore di analisi, impossibile convertire nvarchar in int.


15

Generico

   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;

    }

La prego di farmi sapere che cosa passo come parametro? Selettore <T, TProperty>? Non può essere semplicemente tbl.Rows.Add (oggetto) e non è necessario quel parametro.
GDroid

il selector.Invoke (oggetto) seleziona la proprietà sull'elemento nella maggior parte dei casi è un int, ma consente anche di selezionare una proprietà stringa
Martea

puoi per favore fornire un esempio di come posso mettere il selettore laggiù ?? Ho un elenco <Guid> da passare al processo memorizzato ...
GDroid

guidList.ToTabledValuedParameter (x => x), poiché x è la guida nel tuo caso, il ritorno sarà una DataTable con una colonna (id) con un elenco di guide,
Martea

5

Il modo più pulito di lavorare con esso. Supponendo che la tua tabella sia un elenco di numeri interi chiamati "dbo.tvp_Int" (Personalizza per il tuo tipo di tabella)

Crea questo metodo di estensione ...

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

Ora puoi aggiungere un parametro con valori di tabella in una riga ovunque semplicemente facendo questo:

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

1
cosa succede se paramCollection è NULL? Come passare il tipo vuoto?
Muflix,

2
@Muflix Ovviamente, i metodi di estensione funzionano effettivamente contro istanze null. Quindi aggiungere un semplice if(paramCollection != null)controllo all'inizio del metodo andrà bene
Rhumborl

1
Risposta aggiornata con iniziale -if- check
Shahzad Qureshi

2
Forse un po 'pedante, ma userei IEnumerableinvece che Listnella firma, in questo modo puoi passare tutto ciò che è IEnumerable, non solo elenchi, Dal momento che non stai usando alcuna funzione specifica per List, non vedo davvero un motivo per non noiIEnumerable
Francis Lord,

L'utilizzo dell'elenco ti consente di utilizzare i dati abbreviati.ForEach (), altrimenti dovresti effettivamente scrivere un ciclo foreach. Che potrebbe funzionare anche, ma mi piace scrivere le cose il più breve possibile.
Shahzad Qureshi,

0

Usa questo codice per creare un parametro adatto dal tuo tipo:

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
    };
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.