web-dev-qa-db-fra.com

Le SqlParameter est déjà contenu par un autre SqlParameterCollection - Est-ce que using () {} triche?

Tout en utilisant les blocs using() {} (sic) comme indiqué ci-dessous et en supposant que cmd1 ne vit pas au-delà de la portée du premier bloc using() {}, pourquoi le second bloc devrait-il renvoyer une exception avec le message 

Le SqlParameter est déjà contenu par un autre SqlParameterCollection

Cela signifie-t-il que les ressources et/ou les handles - y compris les paramètres (SqlParameterCollection) - attachés à cmd1 ne sont pas libérés lorsqu'ils sont détruits à la fin du bloc?

using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Test;Integrated Security=True"))
{
    var parameters = new SqlParameter[] { new SqlParameter("@ProductId", SqlDbType.Int ) };

    using(var cmd1 = new SqlCommand("SELECT ProductName FROM Products WHERE ProductId = @ProductId"))
    {
        foreach (var parameter in parameters)
        {
            cmd1.Parameters.Add(parameter);                
        }
        // cmd1.Parameters.Clear(); // uncomment to save your skin!
    }

    using (var cmd2 = new SqlCommand("SELECT Review FROM ProductReviews WHERE ProductId = @ProductId"))
    {
        foreach (var parameter in parameters)
        {
            cmd2.Parameters.Add(parameter);
        }
    }
}

NOTE: Faire cmd1.Parameters.Clear () juste avant la dernière accolade du premier bloc using () {} vous évitera l’exception (et l’embarras possible).

Si vous avez besoin de reproduire, vous pouvez utiliser les scripts suivants pour créer les objets:

CREATE TABLE Products
(
    ProductId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    ProductName nvarchar(32) NOT NULL
)
GO

CREATE TABLE ProductReviews
(
    ReviewId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    ProductId int NOT NULL,
    Review nvarchar(128) NOT NULL
)
GO
67
John Gathogo

Je soupçonne que SqlParameter "sait" de quelle commande il fait partie et que cette information n'est pas effacée lorsque la commande est supprimée, mais est effacée lorsque vous appelez command.Parameters.Clear().

Personnellement, je pense que j’éviterais de réutiliser les objets, mais c’est à vous de décider :)

85
Jon Skeet

L'utilisation de blocs ne garantit pas qu'un objet est "détruit", mais simplement que la méthode Dispose() est appelée. Cela dépend en fait de la mise en œuvre spécifique et, dans ce cas, cela ne vide clairement pas la collection. L'idée est de s'assurer que les ressources non gérées qui ne seraient pas nettoyées par le ramasse-miettes sont correctement éliminées. Comme la collection Parameters n'est pas une ressource non gérée, il n'est pas surprenant qu'elle ne soit pas effacée par la méthode dispose.

8
Ben Robinson

Ajout de cmd.Parameters.Clear (); après l'exécution devrait être bien.

4
Nish

using définit une étendue et effectue l'appel automatique de Dispose() pour lequel nous l'aimons.

Une référence tombant hors de la portée ne fera pas "disparaître" l'objet lui-même si un autre objet y est référé, ce qui sera le cas dans le cas où parameters aura une référence à cmd1.

3
Jon Hanna

J'ai aussi eu le même problème Merci @Jon, basé sur celui que j'ai donné en exemple.

Lorsque j'ai appelé la fonction ci-dessous dans laquelle 2 fois le même paramètre SQL est passé. Lors du premier appel à la base de données, il a été appelé correctement, mais lors de la deuxième tentative, l'erreur ci-dessus a été attribuée.

    public Claim GetClaim(long ClaimId)
    {
        string command = "SELECT * FROM tblClaim "
            + " WHERE RecordStatus = 1 and ClaimId = @ClaimId and ClientId =@ClientId";
        List<SqlParameter> objLSP_Proc = new List<SqlParameter>(){
                new SqlParameter("@ClientId", SessionModel.ClientId),
                new SqlParameter("@ClaimId", ClaimId)
            };

        DataTable dt = GetDataTable(command, objLSP_Proc);
        if (dt.Rows.Count == 0)
        {
            return null;
        }

        List<Claim> list = TableToList(dt);

        command = "SELECT * FROM tblClaimAttachment WHERE RecordStatus = 1 and ClaimId = @ClaimId and ClientId =@ClientId";

        DataTable dt = GetDataTable(command, objLSP_Proc); //gives error here, after add `sqlComm.Parameters.Clear();` in GetDataTable (below) function, the error resolved.


        retClaim.Attachments = new ClaimAttachs().SelectMany(command, objLSP_Proc);
        return retClaim;
    }

C'est la fonction DAL commune

       public DataTable GetDataTable(string strSql, List<SqlParameter> parameters)
        {
            DataTable dt = new DataTable();
            try
            {
                using (SqlConnection connection = this.GetConnection())
                {
                    SqlCommand sqlComm = new SqlCommand(strSql, connection);

                    if (parameters != null && parameters.Count > 0)
                    {
                        sqlComm.Parameters.AddRange(parameters.ToArray());
                    }

                    using (SqlDataAdapter da = new SqlDataAdapter())
                    {
                        da.SelectCommand = sqlComm;
                        da.Fill(dt);
                    }
                    sqlComm.Parameters.Clear(); //this added and error resolved
                }
            }
            catch (Exception ex)
            {                   
                throw;
            }
            return dt;
        }
2
Ajay2707

J'ai rencontré cette erreur particulière parce que j'utilisais les mêmes objets SqlParameter dans le cadre d'une collection SqlParameter pour appeler une procédure plusieurs fois. La raison de cette erreur IMHO est que les objets SqlParameter sont associés à une collection SqlParameter particulière et vous ne pouvez pas utiliser les mêmes objets SqlParameter pour créer une nouvelle collection SqlParameter. 

Donc, au lieu de - 

var param1 = new SqlParameter {DbType = DbType.String, ParameterName = param1, Direction = ParameterDirection.Input, Value = ""};

var param2 = new SqlParameter {DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input, Value = 100}; 

SqlParameter [] sqlParameter1 = new [] {param1, param2};

ExecuteProc (sp_name, sqlParameter1);

/*ERREUR : 

SqlParameter [] sqlParameter2 = new [] {param1, param2};

ExecuteProc (sp_name, sqlParameter2);

* / 

Faire ceci- 

var param3 = new SqlParameter {DbType = DbType.String, ParameterName = param1, Direction = ParameterDirection.Input, Value = param1.Value};

var param4 = new SqlParameter {DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input, Value = param2.Value};

SqlParameter [] sqlParameter3 = new [] {param3, param4}; ExecuteProc (sp_name, sqlParameter3);

1
SaCh

Problème
J'exécutais une procédure stockée SQL Server à partir de C # lorsque j'ai rencontré ce problème: 

Message d'exception [Le SqlParameter est déjà contenu par un autre SqlParameterCollection.]

Cause
Je passais 3 paramètres à ma procédure stockée. J'ai ajouté le 

param = command.CreateParameter();

une seule fois. J'aurais dû ajouter cette ligne pour chaque paramètre, cela signifie 3 fois en tout. 

DbCommand command = CreateCommand(ct.SourceServer, ct.SourceInstance, ct.SourceDatabase);
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[ETL].[pGenerateScriptToCreateIndex]";

DbParameter param = command.CreateParameter();
param.ParameterName = "@IndexTypeID";
param.DbType = DbType.Int16;
param.Value = 1;
command.Parameters.Add(param);

param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "@SchemaName";
param.DbType = DbType.String;
param.Value = ct.SourceSchema;
command.Parameters.Add(param);

param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "@TableName";
param.DbType = DbType.String;
param.Value = ct.SourceDataObjectName;
command.Parameters.Add(param);

dt = ExecuteSelectCommand(command);

Solution
Ajout de la ligne de code suivante pour chaque paramètre

param = command.CreateParameter();
0
Goldfish

J'ai rencontré cette exception car je n'avais pas réussi à instancier un objet paramètre. Je pensais qu'il se plaignait de deux procédures ayant des paramètres avec le même nom. Il se plaignait du même paramètre ajouté deux fois.

            Dim aParm As New SqlParameter()
            aParm.ParameterName = "NAR_ID" : aParm.Value = hfCurrentNAR_ID.Value
            m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
            aParm = New SqlParameter
            Dim tbxDriveFile As TextBox = gvNetworkFileAccess.Rows(index).FindControl("tbxDriveFolderFile")
            aParm.ParameterName = "DriveFolderFile" : aParm.Value = tbxDriveFile.Text
            m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
            **aParm = New SqlParameter()**  <--This line was missing.
            Dim aDDL As DropDownList = gvNetworkFileAccess.Rows(index).FindControl("ddlFileAccess")
            aParm.ParameterName = "AccessGranted" : aParm.Value = aDDL.Text
            **m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)** <-- The error occurred here.
0
Jon Boy