web-dev-qa-db-fra.com

Comment passer sqlparameter à IN ()?

Pour une raison quelconque, le paramètre SQL pour ma clause IN () ne fonctionne pas. Le code se compile bien et la requête fonctionne si je remplace le paramètre par les valeurs réelles

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) 
28
chobo

Vous devez créer un paramètre pour chaque valeur souhaitée dans la clause IN.

Le SQL doit ressembler à ceci:

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

Vous devez donc créer les paramètres et la clause IN dans la boucle foreach.
Quelque chose comme ça (hors de ma tête, non testé):

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++;
}
49
Christian Specht

Si vous utilisez SQL 2008, vous pouvez créer une procédure stockée qui accepte un paramètre de valeur de table (TVP) et utiliser ADO.net pour exécuter la procédure stockée et lui transmettre une table de données:

Tout d'abord, vous devez créer le type dans le serveur SQL:

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

Ensuite, vous devez écrire une procédure stockée qui accepte ce type comme paramètre:

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

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

END

Maintenant, à partir de .net, vous ne pouvez pas utiliser LINQ car il ne prend pas encore en charge les paramètres de valeur de table; vous devez donc écrire une fonction qui fait du vieux ADO.net, prend un DataTable et le passe à la procédure stockée: j'ai écrit une fonction générique que j'utilise qui peut le faire pour n'importe quelle procédure stockée aussi longtemps que cela prend juste le seul paramètre de type table, quel qu'il soit;

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

Ensuite, vous pouvez écrire des fonctions DAL qui utilisent cette fonction utilitaire avec les noms réels des procédures stockées; pour construire l'exemple de votre question, voici à quoi ressemblerait le code:

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

Notez le paramètre "connexion" ci-dessus - J'utilise en fait ce type de fonction dans une classe DataContext partielle pour étendre LINQ DataContext avec ma fonctionnalité TVP, et j'utilise toujours la syntaxe (using var context = new MyDataContext ()) avec ces méthodes.

Cela ne fonctionnera que si vous utilisez SQL Server 2008 - j'espère que vous l'êtes et sinon, cela pourrait être une excellente raison de mettre à niveau! Bien sûr, dans la plupart des cas et dans les grands environnements de production, ce n'est pas si simple, mais FWIW Je pense que c'est la meilleure façon de le faire si vous avez la technologie disponible.

8
Dmitriy Khaykin

Version "nettoyante" possible:

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)));
6
mrogunlana

SQL Server voit votre clause IN comme:

IN ('a,b,c')

À quoi il doit ressembler:

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

Il y a une meilleure façon de faire ce que vous essayez de faire.

  • Si les ID utilisateur se trouvent dans la base de données, la clause IN doit être remplacée par une sous-requête, comme suit:

    IN (SELECT UserID FROM someTable WHERE someConditions)

  • C'est un hack - cela ne fonctionne pas bien avec les index, et vous devez faire attention à ce qu'il fonctionne correctement avec vos données, mais je l'ai utilisé avec succès dans le passé:

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

3
John Pick