C # SQL Server - Übergeben einer Liste an eine gespeicherte Prozedur


111

Ich rufe eine gespeicherte SQL Server-Prozedur aus meinem C # -Code auf:

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();

Mein gespeicherter Prozess ist ziemlich Standard, führt jedoch eine Verknüpfung mit durch QueryTable(daher muss ein gespeicherter Prozess verwendet werden).

Jetzt: Ich möchte List<string>dem Parametersatz eine Liste von Zeichenfolgen hinzufügen . Zum Beispiel sieht meine gespeicherte Proc-Abfrage folgendermaßen aus:

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

Die Liste der Zeichenfolgen ist jedoch dynamisch und einige hundert lang.

Gibt es eine Möglichkeit, eine Liste von Zeichenfolgen List<string>an den gespeicherten Prozess zu übergeben? Oder gibt es einen besseren Weg, dies zu tun?

Vielen Dank, Brett



Welche Version von SQL Server? 2005 ?? 2008 ?? 2008 R2 ?? SQL Server 2008 und neuer hat das Konzept von "tabellenwertigen Parametern" (siehe Redths Antwort für Details)
marc_s

Nicht verwandter Tipp: Der SqlDataReader ist ebenfalls IDisposable und sollte sich in einem usingBlock befinden.
Richardissimo

Antworten:


178

Wenn Sie SQL Server 2008 verwenden, gibt es eine neue Funktion, die als benutzerdefinierter Tabellentyp bezeichnet wird. Hier ist ein Beispiel für die Verwendung:

Erstellen Sie Ihren benutzerdefinierten Tabellentyp:

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

Als nächstes müssen Sie es ordnungsgemäß in Ihrer gespeicherten Prozedur verwenden:

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

Zum Schluss noch ein paar SQL, um es in c # zu verwenden:

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

Um dies von SSMS auszuführen

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
Muss eine Datentabelle definiert werden, um den Wert des Parameters festzulegen? Irgendein anderer leichter Ansatz?
ca9163d9

2
Wir haben es versucht, aber wir haben festgestellt, dass der Nachteil nicht durch das Enitiy-Framework unterstützt wird
Bishoy Hanna

7
Seien Sie vorsichtig mit dieser Lösung, wenn Sie in LINQ2SQL arbeiten, da sie keine vom Benutzer definierten Tabellentypen als Parameter unterstützt !!! Eine Problemumgehung finden Sie in Jon Raynors Antwort, die durch Kommas getrennte Listen und eine Parserfunktion verwendet. Dies hat jedoch auch Nachteile ....
Fazi

3
@Fazi Verwenden Sie in diesem Fall kein Linq2SQL. Es ist vorzuziehen, Verkettung und Analyse von Zeichenfolgen in T-SQL
Panagiotis Kanavos

2
Ja, wie führen Sie dies tatsächlich über SSMS aus?
Sinaesthetic

21

Das typische Muster in dieser Situation besteht darin, die Elemente in einer durch Kommas getrennten Liste zu übergeben und diese dann in SQL in eine Tabelle aufzuteilen, die Sie verwenden können. Die meisten Leute erstellen normalerweise eine bestimmte Funktion, um dies zu tun:

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

Und dann können Sie es in anderen Abfragen verwenden.


17
Lassen Sie mich der Vollständigkeit halber
Veli Gebrev

3
Und was passiert, wenn in einem Ihrer Datenfelder ein Komma steht?
Kevin Panko

3
Begrenzen Sie es stattdessen mit Rohren.
Alex In Paris

@AlexInParis Was ist, wenn sich in einem Ihrer Datenfelder eine Pipe befindet?
RayLoveless

2
Verwenden Sie dann etwas, das nicht in Ihren Datenfeldern enthalten ist. Bereinigen Sie Ihre Daten, falls erforderlich, aber nicht viele Daten, die ich jemals gesehen habe, haben jemals Pipes verwendet. Wenn sie unbedingt notwendig sind, finden Sie ein anderes Zeichen wie ¤ oder §.
Alex in Paris

9

Nein, Arrays / Listen können nicht direkt an SQL Server übergeben werden.

Folgende Optionen stehen zur Verfügung:

  1. Durch Übergeben einer durch Kommas getrennten Liste und anschließende Funktion in SQL wird die Liste aufgeteilt. Die durch Kommas getrennte Liste wird höchstwahrscheinlich als Nvarchar () übergeben.
  2. Übergeben Sie XML und lassen Sie eine Funktion in SQL Server das XML für jeden Wert in der Liste analysieren
  3. Verwenden Sie den neu definierten benutzerdefinierten Tabellentyp (SQL 2008).
  4. Erstellen Sie dynamisch SQL und übergeben Sie die Rohliste als "1,2,3,4" und erstellen Sie die SQL-Anweisung. Dies ist anfällig für SQL-Injection-Angriffe, funktioniert jedoch.

2

Ja, machen Sie den gespeicherten Proc-Parameter zu VARCHAR(...) Und übergeben Sie dann durch Kommas getrennte Werte an eine gespeicherte Prozedur.

Wenn Sie SQL Server 2008 verwenden, können Sie TVP ( Table Value Parameters ) nutzen: SQL 2008 TVP und LINQ, wenn die Struktur von QueryTable komplexer ist als das Array von Zeichenfolgen. Andernfalls wäre dies ein Overkill, da der Tabellentyp in SQl Server erstellt werden muss


2

Erstellen Sie eine Datentabelle mit einer Spalte anstelle von Liste und fügen Sie der Tabelle Zeichenfolgen hinzu. Sie können diese Datentabelle als strukturierten Typ übergeben und einen weiteren Join mit dem Titelfeld Ihrer Tabelle ausführen.


Das ist der richtige Weg. Ich habe tatsächlich eine Tabelle auf der Datenbankseite erstellt und mit bcp write to server geladen.
dier


0

Die einzige Möglichkeit, die mir bekannt ist, besteht darin, eine CSV-Liste zu erstellen und diese dann als Zeichenfolge zu übergeben. Teilen Sie es dann auf der SP-Seite einfach auf und tun Sie, was Sie brauchen.


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

Erstellen Sie einen TYP als Tabelle und nennen Sie ihn "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

Erstellen Sie eine Prozedur wie oben und nennen Sie sie "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();
                    }

Versuchen Sie dies in der c #

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.