SQL Server C #: passaggio di un elenco a una stored procedure


112

Sto chiamando una stored procedure di SQL Server dal mio codice C #:

using (SqlConnection conn = new SqlConnection(connstring))
{
   conn.Open();
   using (SqlCommand cmd = new SqlCommand("InsertQuerySPROC", conn))
   {
      cmd.CommandType = CommandType.StoredProcedure;

      var STableParameter = cmd.Parameters.AddWithValue("@QueryTable", QueryTable);
      var NDistanceParameter = cmd.Parameters.AddWithValue("@NDistanceThreshold", NDistanceThreshold);
      var RDistanceParameter = cmd.Parameters.AddWithValue(@"RDistanceThreshold", RDistanceThreshold);

      STableParameter .SqlDbType = SqlDbType.Structured;
      NDistanceParameter.SqlDbType = SqlDbType.Int;
      RDistanceParameter.SqlDbType = SqlDbType.Int;

      // Execute the query
      SqlDataReader QueryReader = cmd.ExecuteReader();

La mia procedura memorizzata è abbastanza standard ma fa un join con QueryTable(da qui la necessità di utilizzare una procedura memorizzata).

Ora: voglio aggiungere un elenco di stringhe,, List<string>al set di parametri. Ad esempio, la mia query proc archiviata funziona in questo modo:

SELECT feature 
FROM table1 t1 
INNER JOIN @QueryTable t2 ON t1.fid = t2.fid 
WHERE title IN <LIST_OF_STRINGS_GOES_HERE>

Tuttavia, l'elenco delle stringhe è dinamico e lungo alcune centinaia.

C'è un modo per passare un elenco di stringhe List<string>al processo memorizzato ??? O c'è un modo migliore per farlo?

Molte grazie, Brett



Quale versione di SQL Server ?? 2005 ?? 2008 ?? 2008 R2 ?? SQL Server 2008 e versioni successive hanno il concetto di "parametri con valori di tabella" (vedere la risposta di Redth per i dettagli)
marc_s

Suggerimento non correlato: anche SqlDataReader è IDisposable, quindi dovrebbe essere in un usingblocco.
Richardissimo

Risposte:


179

Se utilizzi SQL Server 2008, è disponibile una nuova funzionalità chiamata Tipo di tabella definita dall'utente. Ecco un esempio di come usarlo:

Crea il tuo tipo di tabella definito dall'utente:

CREATE TYPE [dbo].[StringList] AS TABLE(
    [Item] [NVARCHAR](MAX) NULL
);

Successivamente è necessario utilizzarlo correttamente nella procedura memorizzata:

CREATE PROCEDURE [dbo].[sp_UseStringList]
    @list StringList READONLY
AS
BEGIN
    -- Just return the items we passed in
    SELECT l.Item FROM @list l;
END

Finalmente ecco un po 'di sql per usarlo in c #:

using (var con = new SqlConnection(connstring))
{
    con.Open();

    using (SqlCommand cmd = new SqlCommand("exec sp_UseStringList @list", con))
    {
        using (var table = new DataTable()) {
          table.Columns.Add("Item", typeof(string));

          for (int i = 0; i < 10; i++)
            table.Rows.Add("Item " + i.ToString());

          var pList = new SqlParameter("@list", SqlDbType.Structured);
          pList.TypeName = "dbo.StringList";
          pList.Value = table;

          cmd.Parameters.Add(pList);

          using (var dr = cmd.ExecuteReader())
          {
            while (dr.Read())
                Console.WriteLine(dr["Item"].ToString());
          }
         }
    }
}

Per eseguirlo da SSMS

DECLARE @list AS StringList

INSERT INTO @list VALUES ('Apple')
INSERT INTO @list VALUES ('Banana')
INSERT INTO @list VALUES ('Orange')

-- Alternatively, you can populate @list with an INSERT-SELECT
INSERT INTO @list
   SELECT Name FROM Fruits

EXEC sp_UseStringList @list

10
Deve definire un datatable per impostare il valore del parametro? Qualche altro approccio leggero?
ca9163d9

2
L'abbiamo provato ma abbiamo riscontrato che il suo svantaggio non è supportato dal framework
Enitiy

7
FATE ATTENZIONE A QUESTA SOLUZIONE SE UTILIZZATE IN LINQ2SQL, IN QUANTO NON SUPPORTA I TIPI DI TABELLA DEFINITI DALL'UTENTE COME PARAMETRI !!! Una soluzione alternativa può essere trovata nella risposta di Jon Raynor, utilizzando elenchi separati da virgole e una funzione parser, tuttavia questo ha anche degli svantaggi ...
Fazi

3
@ Fazi in questo caso, non utilizzare Linq2SQL. È preferibile la concatenazione di stringhe e l'analisi in T-SQL
Panagiotis Kanavos

2
Sì, quindi come si esegue effettivamente questo da SSMS?
Sinaesthetic

21

Lo schema tipico in questa situazione è passare gli elementi in un elenco delimitato da virgole, quindi in SQL suddividerlo in una tabella che è possibile utilizzare. La maggior parte delle persone di solito crea una funzione specifica per fare questo come:

 INSERT INTO <SomeTempTable>
 SELECT item FROM dbo.SplitCommaString(@myParameter)

E poi puoi usarlo in altre query.


17
fammi inserire un link per un'implementazione dbo.SplitCommaString per completezza: goo.gl/P9ROs
Veli Gebrev

3
E cosa succede quando c'è una virgola in uno dei tuoi campi dati?
Kevin Panko,

3
Delimitalo invece con i tubi.
Alex In Paris

@AlexInParis E se in uno dei campi dati è presente una pipe?
RayLoveless

2
Quindi usa qualcosa che non è nei tuoi campi dati. Pulisci i tuoi dati, se necessario, ma non molti dati che abbia mai visto hanno mai usato pipe. Se sono assolutamente necessari, trova altri caratteri come ¤ o §.
Alex In Paris

9

No, gli array / elenchi non possono essere passati direttamente a SQL Server.

Sono disponibili le seguenti opzioni:

  1. Passare un elenco delimitato da virgole e quindi avere una funzione in SQL divide l'elenco. L'elenco delimitato da virgole sarà molto probabilmente passato come Nvarchar ()
  2. Passa xml e fai in modo che una funzione in SQL Server analizzi l'XML per ogni valore nell'elenco
  3. Usa il nuovo tipo di tabella definito dall'utente (SQL 2008)
  4. Creare dinamicamente l'SQL e passare l'elenco non elaborato come "1,2,3,4" e creare l'istruzione SQL. Questo è soggetto ad attacchi di SQL injection, ma funzionerà.

2

Sì, imposta il parametro Stored proc come VARCHAR(...) E poi passa valori separati da virgole a una stored procedure.

Se si utilizza Sql Server 2008 è possibile sfruttare TVP ( Table Value Parameters ): SQL 2008 TVP e LINQ se la struttura di QueryTable è più complessa dell'array di stringhe altrimenti sarebbe eccessivo perché richiede la creazione del tipo di tabella all'interno di SQl Server


2

Crea un datatable con una colonna invece di List e aggiungi le stringhe alla tabella. Puoi passare questo datatable come tipo strutturato ed eseguire un altro join con il campo del titolo della tua tabella.


questa è la strada da percorrere. In realtà ho creato una tabella sul lato db e ho caricato con bcp write sul server.
dier


0

L'unico modo di cui sono a conoscenza è creare un elenco CSV e quindi passarlo come stringa. Quindi, sul lato SP, dividilo e fai tutto ciò di cui hai bisogno.


0
CREATE TYPE [dbo].[StringList1] AS TABLE(
[Item] [NVARCHAR](MAX) NULL,
[counts][nvarchar](20) NULL);

crea un TYPE come tabella e chiamalo "StringList1"

create PROCEDURE [dbo].[sp_UseStringList1]
@list StringList1 READONLY
AS
BEGIN
    -- Just return the items we passed in
    SELECT l.item,l.counts FROM @list l;
    SELECT l.item,l.counts into tempTable FROM @list l;
 End

Creare una procedura come sopra e denominarla come "UserStringList1"

String strConnection = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString.ToString();
            SqlConnection con = new SqlConnection(strConnection);
            con.Open();
            var table = new DataTable();

            table.Columns.Add("Item", typeof(string));
            table.Columns.Add("count", typeof(string));

            for (int i = 0; i < 10; i++)
            {
                table.Rows.Add(i.ToString(), (i+i).ToString());

            }
                SqlCommand cmd = new SqlCommand("exec sp_UseStringList1 @list", con);


                    var pList = new SqlParameter("@list", SqlDbType.Structured);
                    pList.TypeName = "dbo.StringList1";
                    pList.Value = table;

                    cmd.Parameters.Add(pList);
                    string result = string.Empty;
                    string counts = string.Empty;
                    var dr = cmd.ExecuteReader();

                    while (dr.Read())
                    {
                        result += dr["Item"].ToString();
                        counts += dr["counts"].ToString();
                    }

in c #, prova questo

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.