C # SQL Server - przekazywanie listy do procedury składowanej

112

Wywołuję procedurę składowaną programu SQL Server z mojego kodu 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();

Mój zapisany proces jest dość standardowy, ale łączy go z QueryTable(stąd potrzeba używania przechowywanego procesu).

Teraz: chcę dodać listę ciągów List<string>, do zestawu parametrów. Na przykład moje przechowywane zapytanie proc wygląda następująco:

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

Jednak lista strun jest dynamiczna i kilkaset długich.

Czy istnieje sposób na przekazanie listy ciągów List<string>do przechowywanego procesu ??? Czy jest lepszy sposób, aby to zrobić?

Wielkie dzięki, Brett

Brett
źródło
Prawdopodobnie duplikat stackoverflow.com/questions/209686/ ...
Andrey Agibalov
Jaka wersja programu SQL Server? 2005 ?? 2008 ?? 2008 R2 ?? SQL Server 2008 i nowsze mają koncepcję „parametrów wycenianych w tabeli” (szczegółowe informacje można znaleźć w odpowiedzi firmy Redth)
marc_s
Niepowiązana wskazówka: SqlDataReader jest również IDisposable, więc powinien znajdować się w usingbloku.
Richardissimo

Odpowiedzi:

179

Jeśli używasz SQL Server 2008, dostępna jest nowa funkcja zwana typem tabeli zdefiniowanym przez użytkownika. Oto przykład, jak go używać:

Utwórz typ tabeli zdefiniowany przez użytkownika:

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

Następnie musisz go poprawnie użyć w swojej procedurze składowanej:

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

Na koniec jest trochę sql do użycia w 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());
          }
         }
    }
}

Aby wykonać to z 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
Redth
źródło
10
Czy trzeba zdefiniować datatable, aby ustawić wartość parametru? Jakieś inne lekkie podejście?
ca9163d9
2
Wypróbowaliśmy to, ale okazało się, że jego wada nie jest obsługiwana przez platformę Enitiy
Bishoy Hanna,
7
UWAŻAJ NA TO ROZWIĄZANIE, JEŚLI UŻYWASZ LINQ2SQL, PONIEWAŻ NIE OBSŁUGUJE TO TYPÓW TABEL ZDEFINIOWANYCH PRZEZ UŻYTKOWNIKA JAKO PARAMETRÓW !!! Obejście można znaleźć w odpowiedzi Jona Raynora, używając list oddzielonych przecinkami i funkcji parsera, jednak ma to również wady ...
Fazi,
3
@Fazi w tym przypadku nie używaj Linq2SQL. Lepiej jest łączyć i analizować ciągi znaków w T-SQL
Panagiotis Kanavos
2
Tak, więc jak właściwie to wykonać z SSMS?
Sinaesthetic
21

Typowy wzorzec w tej sytuacji polega na przekazaniu elementów w postaci listy rozdzielanej przecinkami, a następnie w języku SQL podzielenie jej na tabelę, której można użyć. Większość ludzi zwykle tworzy określoną funkcję, aby to zrobić, na przykład:

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

A potem możesz go użyć w innych zapytaniach.

Tejs
źródło
17
wrzucę link do implementacji dbo.SplitCommaString dla kompletności: goo.gl/P9ROs
Veli Gebrev
3
A co się dzieje, gdy w jednym z pól danych znajduje się przecinek?
Kevin Panko
3
Zamiast tego ogranicz go rurami.
Alex In Paris
@AlexInParis A co, jeśli w jednym z pól danych znajduje się potok?
RayLoveless,
2
Następnie użyj czegoś, czego nie ma w polach danych. Wyczyść swoje dane, jeśli to konieczne, ale niewiele danych, które kiedykolwiek widziałem, kiedykolwiek używało potoków. Jeśli są absolutnie konieczne, znajdź inny znak, taki jak ¤ lub §.
Alex In Paris
9

Nie, tablice / listy nie mogą być przekazywane bezpośrednio do SQL Server.

Dostępne są następujące opcje:

  1. Przekazanie listy rozdzielanej przecinkami, a następnie posiadanie funkcji w języku SQL, aby podzielić listę. Lista rozdzielana przecinkami będzie najprawdopodobniej przekazywana jako Nvarchar ()
  2. Przekaż XML i użyj funkcji w SQL Server, aby przeanalizować XML dla każdej wartości na liście
  3. Użyj nowego zdefiniowanego typu tabeli zdefiniowanego przez użytkownika (SQL 2008)
  4. Dynamicznie utwórz kod SQL i prześlij go na liście nieprzetworzonej jako „1,2,3,4” i utwórz instrukcję SQL. Jest to podatne na ataki typu SQL injection, ale zadziała.
Jon Raynor
źródło
2

Tak, ustaw parametr Stored proc na VARCHAR(...) A następnie przekaż wartości oddzielone przecinkami do procedury składowanej.

Jeśli używasz Sql Server 2008, możesz wykorzystać TVP ( Table Value Parameters ): SQL 2008 TVP i LINQ, jeśli struktura QueryTable jest bardziej złożona niż tablica ciągów, w przeciwnym razie byłaby to przesada, ponieważ wymaga utworzenia typu tabeli w SQl Server

sll
źródło
2

Utwórz datatable z jedną kolumną zamiast listy i dodaj ciągi do tabeli. Możesz przekazać ten zbiór danych jako typ strukturalny i wykonać inne sprzężenie z polem tytułu tabeli.

hungryMind
źródło
to jest droga do zrobienia. Właściwie utworzyłem tabelę po stronie db i załadowałem bcp do zapisu na serwerze.
dier
0

Jedynym sposobem, o którym wiem, jest zbudowanie listy CSV i przekazanie jej jako ciągu znaków. Następnie, po stronie SP, po prostu podziel go i rób, co potrzebujesz.

Andrey Agibalov
źródło
0
CREATE TYPE [dbo].[StringList1] AS TABLE(
[Item] [NVARCHAR](MAX) NULL,
[counts][nvarchar](20) NULL);

utwórz TYPE jako tabelę i nazwij ją jako „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

Utwórz procedurę jak powyżej i nazwij ją jako „UserStringList1” s

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

w c #, spróbuj tego

Deepalakshmi
źródło