2012-02-21 25 views
16

Vì một số lý do, tham số Sqlparameter cho mệnh đề IN() của tôi không hoạt động. Đoạn mã biên dịch tốt, và truy vấn hoạt động nếu tôi thay thế các tham số với giá trị thực tếLàm cách nào để chuyển sqlparameter sang 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

Các dấu phẩy phải giữa chuỗi, không nằm trong một chuỗi. –

+0

Dấu phẩy tách biệt mỗi userid – chobo

+0

Phiên bản SQL-Server nào? –

Trả lời

33

Bạn phải tạo một thông số cho mỗi giá trị bạn muốn trong mệnh đề IN.

SQL cần nhìn như thế này:

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

Vì vậy, bạn cần phải tạo ra các thông số các IN khoản trong foreach vòng lặp.
Something như thế này (trong đầu của tôi, chưa được kiểm tra):

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

Hãy nhớ xóa dấu phẩy cuối cùng ở cuối, vì ví dụ ở trên nó sẽ nằm trong (@userId,) –

+0

Bài đăng rất cũ của nó, nhưng chỉ cần thêm một cập nhật để trả lời. để giúp thêm một googler,;) Bạn có thể sử dụng xml làm tham số để có được hiệu ứng tương tự của CSV và trong mệnh đề Query - SELECT userId, tên người dùng FROM Người dùng u1 INNER JOIN @ UsersID.nodes ('/ ID') T (col) ON u1.userId = t.col.value ('.', 'int') – 0cool

3

SQL Server thấy khoản IN của bạn như:

IN ('a,b,c') 

gì nó cần để trông giống như là:

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

Có cách tốt hơn để làm những gì bạn đang cố gắng làm.

  • Nếu của id người dùng đang trong DB, sau đó mệnh đề IN nên được thay đổi để một subquery, như vậy:

    IN (SELECT UserID FROM someTable WHERE someConditions)

  • Đây là một hack - nó không hoạt động tốt với các chỉ mục và bạn phải cẩn thận với hoạt động của dữ liệu đó, nhưng tôi đã sử dụng thành công trong quá khứ:

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

+0

+1 cho bản hack của bạn. Ngay cả khi nó có thể bắt buộc quét toàn bộ và ngăn người tối ưu thực hiện công việc của mình, đó là một mẹo thông minh, điều đó cũng có thể sử dụng được với Access. –

+0

@ John: Tôi đã thử điều này: 'IN (@param)' và sau đó 'command.Parameters.AddWithValue (" @ param "," 'a', 'b', 'c' ");' nhưng điều này là không thành công . Bạn có thể xin lời khuyên về điều này. – Praveen

+0

@ user1671639 Nếu bạn luôn có 3 tham số, thì bạn có thể sử dụng 'IN (@ param1, @ param2, @ param3)' và sau đó 'command.Parameters.AddWithValue (" @ param1 "," a "); command.Parameters.AddWithValue ("@ param2", "b"); command.Parameters.AddWithValue ("@ param3", "c"); '. Nếu bạn không luôn có 3 giá trị, có lẽ bạn nên hỏi một câu hỏi stackoverflow.com mới, cung cấp đầy đủ chi tiết và chỉ cho tôi câu hỏi mới. Tôi đặt cược nhiều người sẽ cố gắng trả lời ngay lập tức. –

7

Nếu bạn đang sử dụng SQL 2008, bạn có thể tạo một stored procedure mà chấp nhận một bảng Parameter Quý (TVP) và sử dụng để thực hiện ADO.net các thủ tục lưu trữ và vượt qua một DataTable với nó:

Trước tiên, bạn cần tạo gõ vào SQL server:

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

Sau đó, bạn cần phải viết một thủ tục lưu trữ mà chấp nhận loại hình này như một tham số:

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

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

END 

Bây giờ từ .net, bạn không thể sử dụng LINQ vì nó không hỗ trợ Thông số giá trị bảng; vì vậy bạn phải viết một hàm có ADO cũ đơn giản.net, lấy một DataTable và chuyển nó đến thủ tục đã lưu trữ: Tôi đã viết một hàm generic mà tôi sử dụng có thể thực hiện điều này cho bất kỳ thủ tục được lưu trữ nào miễn là nó chỉ lấy tham số được gõ một bảng, bất kể nó là gì;

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

Sau đó, bạn có thể viết hàm DAL sử dụng chức năng tiện ích này với tên thực tế của các thủ tục được lưu trữ; để xây dựng trên các ví dụ trong câu hỏi của bạn, đây là những gì mã sẽ trông như thế:

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

Lưu ý các "kết nối" tham số ở trên - Tôi thực sự sử dụng loại hàm trong một lớp DataContext một phần mở rộng LINQ DataContext với chức năng TVP của tôi, và vẫn sử dụng cú pháp (sử dụng var context = new MyDataContext()) với các phương thức này.

Điều này sẽ chỉ hoạt động nếu bạn đang sử dụng SQL Server 2008 - hy vọng bạn đang có và nếu không, đây có thể là một lý do tuyệt vời để nâng cấp! Tất nhiên trong hầu hết các trường hợp và môi trường sản xuất lớn này không phải là dễ dàng, nhưng FWIW tôi nghĩ rằng đây là cách tốt nhất để làm điều này nếu bạn có công nghệ có sẵn.

+0

Nếu hầu hết các bảng của bạn sử dụng cùng loại cho PK của chúng, bạn có thể không tạo ra một tham số ID có thể tái sử dụng tổng quát UDT không? Giống như: CREATE TYPE [dbo]. [Udt_IntId] AS BẢNG ([Id] [int] NULL) để được sử dụng lại trong trường hợp * bất kỳ * nơi bạn cần thực hiện mệnh đề sql IN trên id khóa chính int? – Pxtl

4

thể "sạch" phiên bản:

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))); 
Các vấn đề liên quan