2012-02-21 13 views
16

Z jakiegoś powodu parametr Sql dla mojego warunku IN() nie działa. Kod kompiluje grzywny, a kwerenda działa gdybym zastąpić parametr z rzeczywistymi wartościamiJak przekazać sqlparameter do IN()?

StringBuilder sb = new StringBuilder(); 
      foreach (User user in UserList) 
      { 
       sb.Append(user.UserId + ","); 
      } 

      string userIds = sb.ToString(); 
      userIds = userIds.TrimEnd(new char[] { ',' }); 


SELECT userId, username 
FROM Users 
WHERE userId IN (@UserIds) 
+0

Przecinki mają być między strunami, a nie w obrębie łańcucha. –

+0

Przecinki oddzielają każdy identyfikator użytkownika. – chobo

+0

Jaką wersję serwera SQL? –

Odpowiedz

33

Musisz utworzyć jeden parametr dla każdej wartości, którą chcesz w klauzuli IN.

SQL musi wyglądać następująco:

SELECT userId, username 
FROM Users 
WHERE userId IN (@UserId1, @UserId2, @UserId3, ...) 

Więc trzeba tworzyć parametry i klauzula w pętli foreachIN.
Coś jak to (z mojej głowy, niesprawdzonych):

StringBuilder sb = new StringBuilder(); 
int i = 1; 

foreach (User user in UserList) 
{ 
    // IN clause 
    sb.Append("@UserId" + i.ToString() + ","); 

    // parameter 
    YourCommand.Parameters.AddWithValue("@UserId" + i.ToString(), user.UserId); 

    i++; 
} 
+5

Pamiętaj, usuń ostatni przecinek na końcu, ponieważ z powyższym przykładem będzie w (@userId,) –

+0

Jest to bardzo stary post, ale tylko jedna aktualizacja do odpowiedzi. aby pomóc jeszcze jednemu użytkownikowi google,;) Możesz użyć xml jako parametru, aby uzyskać podobny efekt CSV i w klauzuli Zapytanie - SELECT userId, username FROM Users u1 INNER JOIN @ UsersID.nodes ('/ ID') T (col) ON u1.userId = t.col.value ('.', 'int') – 0cool

3

SQL Server widzi klauzuli IN jak:

IN ('a,b,c') 

Co musi wyglądać to:

IN ('a','b','c') 

Istnieje lepszy sposób robienia tego, co próbujesz zrobić.

  • Jeśli identyfikator użytkownika są w DB, wówczas klauzula IN należy zmienić podkwerendzie, tak:

    IN (SELECT UserID FROM someTable WHERE someConditions)

  • Jest to hack - nie dobrze pracować z indeksami i trzeba uważać, to działa w porządku ze swoimi danymi, ale użyłem go z powodzeniem w przeszłości:

    @UserIDs LIKE '%,' + UserID + ',%' -- also requires @UserID to begin and end with a comma

+0

+1 za włamanie. Nawet jeśli prawdopodobnie wymusza pełne skanowanie i uniemożliwia optymalizatorowi wykonanie swojej pracy, jest to sprytna sztuczka, którą można wykorzystać również w programie Access. –

+0

@John: Próbowałem tego: 'IN (@param)', a następnie 'command.Parameters.AddWithValue (" @param "," 'a "," b "," c ""); 'ale to się nie udaje . Czy możesz doradzić w tej sprawie. – Praveen

+0

@ user1671639 Jeśli zawsze masz 3 parametry, możesz użyć 'IN (@ param1, @ param2, @ param3)', a następnie 'command.Parameters.AddWithValue (" @ param1 "," a "); command.Parameters.AddWithValue ("@ param2", "b"); command.Parameters.AddWithValue ("@ param3", "c"); '. Jeśli nie zawsze masz 3 wartości, być może powinieneś zadać nowe pytanie stackoverflow.com, podać wystarczającą ilość szczegółów i wskazać mi nowe pytanie. Założę się, że kilka osób spróbuje odpowiedzieć od razu. –

7

Jeśli używasz SQL Server 2008, można utworzyć procedurę przechowywaną, która przyjmuje stole Ceniąca parametru (TVP) i użyć ADO.NET do wykonywania procedura przechowywana i przekazać DataTable do niego:

Najpierw trzeba stworzyć tego typu w serwerze SQL:

CREATE TYPE [dbo].[udt_UserId] AS TABLE(
    [UserId] [int] NULL 
) 

Następnie trzeba napisać procedurę przechowywaną, która przyjmuje ten typ jako parametr:

CREATE PROCEDURE [dbo].[usp_DoSomethingWithTableTypedParameter] 
(
    @UserIdList udt_UserId READONLY 
) 
AS 
BEGIN 

     SELECT userId, username 
     FROM Users 
     WHERE userId IN (SELECT UserId FROM @UserIDList) 

END 

Teraz z .net nie można używać LINQ, ponieważ nie obsługuje on jeszcze wartości z tabeli; więc musisz napisać funkcję, która wykonuje zwykły ADO.net, pobiera DataTable i przekazuje go do procedury przechowywanej: Napisałem ogólną funkcję, której używam, która może to zrobić dla dowolnej procedury przechowywanej, o ile bierze tylko jeden wpisany tablicą parametr, niezależnie od tego, jaki jest;

public static int ExecStoredProcWithTVP(DbConnection connection, string storedProcedureName, string tableName, string tableTypeName, DataTable dt) 
    { 
     using (SqlConnection conn = new SqlConnection(connection.ConnectionString)) 
     { 
      SqlCommand cmd = new SqlCommand(storedProcedureName, conn); 
      cmd.CommandType = CommandType.StoredProcedure; 

      SqlParameter p = cmd.Parameters.AddWithValue(tableName, dt); 
      p.SqlDbType = SqlDbType.Structured; 
      p.TypeName = tableTypeName; 

      conn.Open(); 
      int rowsAffected = cmd.ExecuteNonQuery(); // or could execute reader and pass a Func<T> to perform action on the datareader; 
      conn.Close(); 

      return rowsAffected; 
     } 
    } 

Następnie można pisać funkcje DAL, które używają tej funkcji narzędzia z rzeczywistymi nazwami procedur składowanych; opierać się na przykład na pytanie, o to co kod wyglądałby następująco:

public int usp_DoSomethingWithTableTypedParameter(List<UserID> userIdList) 
    { 
     DataTable dt = new DataTable(); 
     dt.Columns.Add("UserId", typeof(int)); 

     foreach (var userId in updateList) 
     { 
      dt.Rows.Add(new object[] { userId }); 
     } 

     int rowsAffected = ExecStoredProcWithTVP(Connection, "usp_DoSomethingWithTableTypedParameter", "@UserIdList", "udt_UserId", dt); 
     return rowsAffected; 
    } 

Uwaga parametr „Połączenie” powyżej - I rzeczywiście korzystać z tego typu funkcji w częściowej klasy DataContext rozszerzenie LINQ DataContext z mojej funkcji TVP i nadal wykorzystuję składnię (używając var context = new MyDataContext()) tymi metodami.

To będzie działać tylko, jeśli używasz SQL Server 2008 - mam nadzieję, że jesteś, a jeśli nie, może to być świetnym powodem do aktualizacji! Oczywiście w większości przypadków i w dużych środowiskach produkcyjnych nie jest to takie proste, ale FWIW uważam, że jest to najlepszy sposób na zrobienie tego, jeśli masz dostępną technologię.

+0

Jeśli większość twoich tabel używa tego samego typu dla ich PK, czy nie mógłbyś stworzyć ogólnego parametru UDT do wielokrotnego użytku? Podobnych: CREATE TYPE [dbo]. [Udt_IntId] AS TABLE ([Id] [int] NULL) do ponownego użycia w * any * przypadku, gdzie trzeba zrobić sql IN klauzula na int klucza klucza id? – Pxtl

4

Possible "czystsze" wersja:

StringBuilder B = new StringBuilder(); 
for (int i = 0; i < UserList.Count; i++) 
    YourCommand.Parameters.AddWithValue("@UserId" + i.ToString(), UserList[i].UserId); 
B.Append(String.Join(",", YourCommand.Parameters.Select(x => x.Name))); 
Powiązane problemy