2012-02-21 18 views
16

Por algún motivo, el parámetro Sql para mi cláusula IN() no funciona. El código se compila bien, y la consulta funciona si sustituyo el parámetro con los valores realesCómo pasar sqlparameter a 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

Las comas tienen que ser entre las cuerdas, no dentro de una cadena. –

+0

Las comas separan cada userid – chobo

+0

¿Qué versión de SQL-Server? –

Respuesta

33

Debe crear un parámetro para cada valor que desee en la cláusula IN.

El SQL tiene que tener este aspecto:

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

por lo que necesita para crear los parámetros yIN la cláusula en el bucle foreach.
Algo como esto (de la cabeza, no probado):

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

Recuerda eliminar la última coma al final, porque con el ejemplo anterior estará en (@userId,) –

+0

Es una publicación muy antigua, pero solo una actualización más para responder. para ayudar a un usuario más,;) Puede usar xml como parámetro para obtener un efecto similar de CSV y en la cláusula Query - SELECT ID de usuario, nombre de usuario FROM Users u1 INNER JOIN @ UsersID.nodes ('/ ID') T (col) ON u1.userId = t.col.value ('.', 'int') – 0cool

3

SQL Server ve su cláusula de IN como:

IN ('a,b,c') 

Lo que necesita para parecerse es:

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

Hay una manera mejor de hacer lo que estás tratando de hacer.

  • Si el ID de usuario de están en la base de datos, entonces la cláusula IN se debe cambiar a una sub consulta, así:

    IN (SELECT UserID FROM someTable WHERE someConditions)

  • Este es un truco - no lo hace trabajar bien con los índices, y hay que tener cuidado de que funciona bien con sus datos, pero he utilizado con éxito en el pasado:

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

+0

+1 para su hack. Aunque probablemente fuerce un escaneo completo, y evite que el optimizador haga su trabajo, es un truco inteligente, que también se puede usar con Access. –

+0

@John: He intentado esto: 'IN (@param)' y luego 'command.Parameters.AddWithValue (" @ param "," 'a', 'b', 'c' ");' pero esto no ha funcionado . ¿Puedes por favor consejos sobre esto? – Praveen

+0

@ user1671639 Si siempre tiene 3 parámetros, puede usar 'IN (@ param1, @ param2, @ param3)' y luego 'command.Parameters.AddWithValue (" @ param1 "," a "); command.Parameters.AddWithValue ("@ param2", "b"); command.Parameters.AddWithValue ("@ param3", "c"); '. Si no siempre tiene 3 valores, tal vez debería hacer una nueva pregunta de stackoverflow.com, proporcionar suficientes detalles y señalarme la nueva pregunta. Apuesto a que varias personas intentarán responder de inmediato. –

7

Si está utilizando SQL Server 2008, puede crear un procedimiento almacenado que acepta un parámetro valorado Tabla (TVP) y el uso de ADO.net para ejecutar el procedimiento y se almacena pasar una tabla de datos a la misma:

en primer lugar, es necesario crear el tipo de servidor SQL:

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

Entonces, es necesario escribir un procedimiento almacenado que acepta este tipo como un parámetro:

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

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

END 

Ahora desde .net, no puede usar LINQ ya que aún no es compatible con los parámetros de tabla de valores; por lo tanto, debe escribir una función que haga simple ADO antiguo.net, toma una DataTable y la pasa al procedimiento almacenado: he escrito una función genérica que uso que puede hacer esto para cualquier procedimiento almacenado, siempre y cuando solo tome el parámetro de una tabla, independientemente de lo que sea;

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

Luego puede escribir las funciones DAL que utilizan esta función de utilidad con los nombres reales de los procedimientos almacenados; para construir en el ejemplo en su pregunta, esto es lo que el código se vería así:

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

Nota el parámetro de "conexión" más arriba - en realidad yo uso este tipo de función en una clase DataContext parcial para extender LINQ DataContext con mi funcionalidad TVP, y aún uso la sintaxis (usando var context = new MyDataContext()) con estos métodos.

Esto solo funcionará si está usando SQL Server 2008 - con suerte lo es y, de lo contrario, ¡esta podría ser una buena razón para actualizar! Por supuesto, en la mayoría de los casos y en entornos de producción grandes esto no es tan fácil, pero FWIW creo que esta es la mejor manera de hacerlo si tiene la tecnología disponible.

+0

Si la mayoría de las tablas usan el mismo tipo para su PK, ¿no podría hacer un parámetro UDT reutilizable generalizado? Me gusta: CREATE TYPE [dbo]. [Udt_IntId] AS TABLE ([Id] [int] NULL) para reutilizar en * any * caso donde necesite hacer una cláusula sql IN en la identificación de la clave principal int? – Pxtl

4

Posible "más limpia" versió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))); 
Cuestiones relacionadas