2008-10-08 10 views
24

Tengo un servicio web que se pasa una serie de entradas. Me gustaría hacer la declaración de selección de la siguiente manera, pero sigo recibiendo errores. ¿Debo cambiar la matriz por una cadena?WHERE IN (matriz de ID)

[WebMethod] 
public MiniEvent[] getAdminEvents(int buildingID, DateTime startDate) 
{  
    command.CommandText = @"SELECT id, 
          startDateTime, endDateTime From 
          tb_bookings WHERE buildingID IN 
          (@buildingIDs) AND startDateTime <= 
          @fromDate"; 

    SqlParameter buildID = new SqlParameter("@buildingIDs", buildingIDs); 
} 
+3

Si está utilizando SQL Server 2008, puede usar un parámetro con valores de tabla que le permitirá pasar múltiples valores en un solo parámetro. Lo más probable es que realice una unión en lugar de "where in()", aunque cualquiera de ellos funcionará. [Parámetros con valores de tabla] (http://msdn.microsoft.com/en-us/library/bb675163.aspx) – ulty4life

+0

Hmmm ... no se dio cuenta de que la pregunta se realizó en octubre de 2008 al principio. Probablemente no ... Con suerte, esto al menos ayudará a cualquiera que golpee esto como resultado de búsqueda. – ulty4life

Respuesta

29

No puede (desafortunadamente) hacer eso. Un SQL parámetro sólo puede ser un solo valor, por lo que tendría que hacer:

WHERE buildingID IN (@buildingID1, @buildingID2, @buildingID3...) 

cual, por supuesto, requiere que saber cuántos identificadores de edificio hay, o para construir dinámicamente la consulta.

* Como solución, he hecho lo siguiente:

WHERE buildingID IN (@buildingID) 

command.CommandText = command.CommandText.Replace(
    "@buildingID", 
    string.Join(buildingIDs.Select(b => b.ToString()), ",") 
); 

que sustituirá el texto de la declaración con los números, terminando como algo así como:

WHERE buildingID IN (1,2,3,4) 
  • Tenga en cuenta que esto se está acercando a una vulnerabilidad de inyección Sql, pero dado que es una matriz int es segura. Las cadenas arbitrarias son no seguras, pero no hay forma de incrustar sentencias Sql en un entero (o datetime, boolean, etc.).
+0

Una advertencia con esto es que si 'buildingIDs' está vacío, ese código producirá un error de sintaxis y, por lo tanto, tendrá que tratar ese caso por separado. –

6

NOTA: No suelo utilizar consultas sin parametrizar. EN ESTA INSTANCIA, sin embargo, dado que se trata de una matriz de enteros, usted podría hacer tal cosa y sería más eficiente. Sin embargo, dado que todo el mundo parece querer degradar la respuesta porque no cumple con sus criterios de asesoramiento válido, presentaré otra respuesta que funciona de manera horrible, pero probablemente se ejecutará en LINK2SQL.

Suponiendo, como sus estados de interrogación, que tiene una matriz de enteros, puede utilizar el siguiente código para devolver una cadena que contiene una lista separada por comas, que SQL aceptaría:

private string SQLArrayToInString(Array a) 
{ 
StringBuilder sb = new StringBuilder(); 
for (int i = 0; i < a.GetUpperBound(0); i++) 
    sb.AppendFormat("{0},", a.GetValue(i)); 
string retVal = sb.ToString(); 
return retVal.Substring(0, retVal.Length - 1); 
} 

Entonces, yo recomendaría usted se salta tratando de parametrizar el comando dado que esta es una matriz de enteros y sólo tiene que utilizar:

command.CommandText = @"SELECT id, 
      startDateTime, endDateTime From 
      tb_bookings WHERE buildingID IN 
      (" + SQLArrayToInString(buildingIDs) + ") AND startDateTime <= 
      @fromDate"; 
+2

No, no omita las declaraciones parametrizadas, ¡ese es un consejo peligroso para dar! – Meff

+0

Estoy de acuerdo. No sugiera que las personas usen consultas sin parámetros. –

+0

¿Alguien podría ampliar lo que hace que esto sea tan peligroso? – Jeffrey

0

[WebMethod]

MiniEvent [] getAdminEvents públicas (int buildingID, DateTime startDate)

...

SqlParameter BUILDID = new SqlParameter ("@", buildingIDs buildingIDs);

Quizás estoy siendo demasiado detallado, pero este método acepta una sola int, no una matriz de entradas. Si espera pasar una matriz, necesitará actualizar su definición de método para tener una matriz int. Una vez que obtenga esa matriz, necesitará convertir la matriz a una cadena si planea usarla en una consulta SQL.

8

Primero vas a necesitar una función y un sproc. La función será dividir los datos y devolver una tabla:

CREATE function IntegerCommaSplit(@ListofIds nvarchar(1000)) 
returns @rtn table (IntegerValue int) 
AS 
begin 
While (Charindex(',',@ListofIds)>0) 
Begin 
    Insert Into @Rtn 
    Select ltrim(rtrim(Substring(@ListofIds,1,Charindex(',',@ListofIds)-1))) 
    Set @ListofIds = Substring(@ListofIds,Charindex(',',@ListofIds)+len(','),len(@ListofIds)) 
end 
Insert Into @Rtn 
    Select ltrim(rtrim(@ListofIds)) 
return 
end 

A continuación, tiene un sproc de usar que:

create procedure GetAdminEvents 
    @buildingids nvarchar(1000), 
    @startdate datetime 
as 
SELECT id,startDateTime, endDateTime From 
      tb_bookings t INNER JOIN 
dbo.IntegerCommaSplit(@buildingids) i 
on i.IntegerValue = t.id 
WHERE startDateTime <= @fromDate 

Por último, su código:

[WebMethod] 
     public MiniEvent[] getAdminEvents(int[] buildingIDs, DateTime startDate) 
     command.CommandText = @"exec GetAdminEvents"; 
SqlParameter buildID= new SqlParameter("@buildingIDs", buildingIDs); 

que va más allá cuál es tu pregunta, pero hará lo que necesites.

Nota: si pasa algo que no sea int, toda la función de la base de datos fallará. Dejo el manejo de errores para eso como un ejercicio para el usuario final.

3

Un método XML súper rápida que no requiere ningún código inseguro o funciones definidas por el usuario:

Se puede utilizar un procedimiento almacenado y pasar la lista separada por comas de identificadores de construcción:

Declare @XMLList xml 
SET @XMLList=cast('<i>'+replace(@buildingIDs,',','</i><i>')+'</i>' as xml) 
SELECT x.i.value('.','varchar(5)') from @XMLList.nodes('i') x(i)) 

Todo el crédito va a Guru Brad Schulz's Blog

0

Puede usar esto. Ejecutar en SQL Server para crear una función en su base de datos (sólo una vez):

IF EXISTS(
    SELECT * 
    FROM sysobjects 
    WHERE name = 'FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT') 
BEGIN 
    DROP FUNCTION FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT 
END 
GO 

CREATE FUNCTION [dbo].FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT (@IDList VARCHAR(8000)) 
RETURNS 
    @IDListTable TABLE (ID INT) 
AS 
BEGIN 

    DECLARE 
     [email protected] VARCHAR(100), 
     @LastCommaPosition INT, 
     @NextCommaPosition INT, 
     @EndOfStringPosition INT, 
     @StartOfStringPosition INT, 
     @LengthOfString INT, 
     @IDString VARCHAR(100), 
     @IDValue INT 

    --SET @IDList = '11,12,113' 

    SET @LastCommaPosition = 0 
    SET @NextCommaPosition = -1 

    IF LTRIM(RTRIM(@IDList)) <> '' 
    BEGIN 

     WHILE(@NextCommaPosition <> 0) 
     BEGIN 

      SET @NextCommaPosition = CHARINDEX(',',@IDList,@LastCommaPosition + 1) 

      IF @NextCommaPosition = 0 
       SET @EndOfStringPosition = LEN(@IDList) 
      ELSE 
       SET @EndOfStringPosition = @NextCommaPosition - 1 

      SET @StartOfStringPosition = @LastCommaPosition + 1 
      SET @LengthOfString = (@EndOfStringPosition + 1) - @StartOfStringPosition 

      SET @IDString = SUBSTRING(@IDList,@StartOfStringPosition,@LengthOfString)     

      IF @IDString <> '' 
       INSERT @IDListTable VALUES(@IDString) 

      SET @LastCommaPosition = @NextCommaPosition 

     END --WHILE(@NextCommaPosition <> 0) 

    END --IF LTRIM(RTRIM(@IDList)) <> '' 

    RETURN 

ErrorBlock: 

    RETURN 

END --FUNCTION 

Después de crear la función que tiene que llamar a esto en su código:

command.CommandText = @"SELECT id, 
         startDateTime, endDateTime From 
         tb_bookings WHERE buildingID IN 
         (SELECT ID FROM FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT(@buildingIDs))) AND startDateTime <= 
         @fromDate"; 

command.Parameters.Add(new SqlParameter(){ 
          DbType = DbType.String, 
          ParameterName = "@buildingIDs", 
          Value = "1,2,3,4,5" //Enter the parameters here separated with commas 
         }); 

Esta función obtener el texto comas internas en "matriz" y hacer una tabla con estos valores como int, llamada ID. Cuando esta función está en su base de datos, puede usarla en cualquier proyecto.


Gracias a Microsoft MSDN.

Igo S Ventura

Microsoft MVA

Sistema Ari de Sá

[email protected]

P.S .: Soy de Brasil. Disculpa mi inglés ... XD

1

Utilizo ese enfoque y funciona para mí.

Mi variable act = mi lista de identificaciones en la cadena.

acto = "1, 2, 3, 4"

command = new SqlCommand("SELECT x FROM y WHERE x.id IN (@actions)", conn);  
command.Parameters.AddWithValue("@actions", act); 
command.CommandText = command.CommandText.Replace("@actions", act); 
0

Aquí hay una solución Linq pensé arriba. Insertará automáticamente todos los elementos en la lista como parámetros @ item0, @ item1, @ item2, @ item3, etc.

[WebMethod] 
public MiniEvent[] getAdminEvents(Int32[] buildingIDs, DateTime startDate) 
{ 
    // Gets a list with numbers from 0 to the max index in buildingIDs, 
    // then transforms it into a list of strings using those numbers. 
    String idParamString = String.Join(", ", (Enumerable.Range(0, buildingIDs.Length).Select(i => "@item" + i)).ToArray()); 
    command.CommandText = @"SELECT id, 
         startDateTime, endDateTime From 
         tb_bookings WHERE buildingID IN 
         (" + idParamString + @") AND startDateTime <= 
         @fromDate"; 
    // Reproduce the same parameters in idParamString 
    for (Int32 i = 0; i < buildingIDs.Length; i++) 
      command.Parameters.Add(new SqlParameter ("@item" + i, buildingIDs[i])); 
    command.Parameters.Add(new SqlParameter("@fromDate", startDate); 
    // the rest of your code... 
} 
Cuestiones relacionadas