SqlParameter è già contenuto da un altro SqlParameterCollection - L'uso di () {} cheat?


87

Mentre si utilizzano i using() {}blocchi (sic) come mostrato di seguito e supponendo che cmd1non vivi oltre l'ambito del primo using() {}blocco, perché il secondo blocco dovrebbe generare un'eccezione con il messaggio

SqlParameter è già contenuto da un altro SqlParameterCollection

Significa che le risorse e / o gli handle - inclusi i parametri ( SqlParameterCollection) - allegati cmd1non vengono rilasciati quando vengono distrutti alla fine del blocco?

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

NOTA: L' esecuzione di cmd1.Parameters.Clear () appena prima dell'ultima parentesi graffa del primo blocco using () {} ti salverà dall'eccezione (e dal possibile imbarazzo).

Se è necessario riprodurre, è possibile utilizzare i seguenti script per creare gli oggetti:

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

Lo vedo anche io, ma quella correzione non ha funzionato. Frustrante. E sto usando solo un singolo oggetto cmd, non riutilizzato. È racchiuso in un ciclo di nuovi tentativi asincroni, quindi è probabilmente la stessa causa principale che non viene evitata allo stesso modo.
Ed Williams

Risposte:


111

Ho il sospetto che SqlParameter"sappia" di quale comando fa parte e che tale informazione non venga cancellata quando il comando viene eliminato, ma viene cancellata quando chiami command.Parameters.Clear().

Personalmente penso che eviterei di riutilizzare gli oggetti in primo luogo, ma dipende da te :)


2
Grazie. Sospettavo che fosse così. Significherebbe anche che SqlParameter si sta associando a un oggetto disposto che non sono sicuro che sia una buona cosa
John Gathogo,

@JohnGathogo: Beh, è ​​associato a un oggetto che viene eliminato dopo che l'associazione è stata formata. Non è l'ideale, certamente.
Jon Skeet

11
Una nota per gli altri. Ho dovuto eseguire la Clearprima di uscire dal primo usingblocco. Farlo quando si entra nel mio 2 ° usingblocco ha comunque generato questo errore.
Snekse

@ JonSkeet è idiota dover ricreare lo stesso set di parametri, solo per fare un'altra query. sembra un accoppiamento stretto
simbionte

9

L'uso dei blocchi non garantisce che un oggetto venga "distrutto", ma semplicemente che il Dispose()metodo viene chiamato. Ciò che effettivamente fa dipende dalla specifica implementazione e in questo caso chiaramente non svuota la collezione. L'idea è di garantire che le risorse non gestite che non verrebbero ripulite dal Garbage Collector vengano eliminate correttamente. Poiché la raccolta Parameters non è una risorsa non gestita, non è del tutto sorprendente che non venga cancellata dal metodo dispose.


7

Aggiunta di cmd.Parameters.Clear (); dopo l'esecuzione dovrebbe andare bene.


3

usingdefinisce un ambito e fa la chiamata automatica di Dispose()cui lo amiamo.

Un riferimento che esce dallo scope non farà "sparire" l'oggetto stesso se un altro oggetto ha un riferimento ad esso, che in questo caso sarà il caso di parametersavere un riferimento a cmd1.


2

Ho anche ricevuto lo stesso problema Grazie @ Jon, sulla base di questo ho fornito l'esempio.

Quando ho chiamato la funzione di seguito in cui sono passati 2 volte lo stesso parametro sql. Nella prima chiamata al database, è stato chiamato correttamente, ma nella seconda volta è stato fornito l'errore precedente.

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

Questa è la funzione DAL comune

       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

Ho riscontrato questo errore particolare perché stavo utilizzando gli stessi oggetti SqlParameter come parte di una raccolta SqlParameter per chiamare una procedura più volte. Il motivo di questo errore IMHO è che gli oggetti SqlParameter sono associati a una particolare raccolta SqlParameter e non è possibile utilizzare gli stessi oggetti SqlParameter per creare una nuova raccolta SqlParameter.

Quindi, invece di questo:

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

/*ERROR : 
SqlParameter[] sqlParameter2 = new[] { param1, param2 };
ExecuteProc(sp_name, sqlParameter2);
*/ 

Fai questo:

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

0

Ho riscontrato questa eccezione perché non ero riuscito a creare un'istanza di un oggetto parametro. Pensavo si stesse lamentando di due procedure con parametri con lo stesso nome. Si lamentava che lo stesso parametro veniva aggiunto due volte.

            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

Problema
stavo eseguendo una stored procedure SQL Server da C # quando ho riscontrato questo problema:

Messaggio di eccezione [SqlParameter è già contenuto da un altro SqlParameterCollection.]

Perché
stavo passando 3 parametri alla mia stored procedure. Ho aggiunto il file

param = command.CreateParameter();

solo una volta in tutto. Avrei dovuto aggiungere questa riga per ogni parametro, significa 3 volte in tutto.

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

Soluzione
Aggiungendo la seguente riga di codice per ogni parametro

param = command.CreateParameter();

0

Ecco come l'ho fatto!

        ILease lease = (ILease)_SqlParameterCollection.InitializeLifetimeService();
        if (lease.CurrentState == LeaseState.Initial)
        {
            lease.InitialLeaseTime = TimeSpan.FromMinutes(5);
            lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
            lease.RenewOnCallTime = TimeSpan.FromMinutes(2);
            lease.Renew(new TimeSpan(0, 5, 0));
        }

0

Se stai usando EntityFramework

Ho avuto anche questa stessa eccezione. Nel mio caso, stavo chiamando SQL tramite un DBContext EntityFramework. Quello che segue è il mio codice e come l'ho risolto.

Codice rotto

string sql = "UserReport @userID, @startDate, @endDate";

var sqlParams = new Object[]
{
    new SqlParameter { ParameterName= "@userID", Value = p.UserID, SqlDbType = SqlDbType.Int, IsNullable = true }
    ,new SqlParameter { ParameterName= "@startDate", Value = p.StartDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
    ,new SqlParameter { ParameterName= "@endDate", Value = p.EndDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
};

IEnumerable<T> rows = ctx.Database.SqlQuery<T>(sql,parameters);

foreach(var row in rows) {
    // do something
}

// the following call to .Count() is what triggers the exception
if (rows.Count() == 0) {
    // tell user there are no rows
}

Nota: la chiamata precedente a SqlQuery<T>()restituisce effettivamente a DbRawSqlQuery<T>, che implementaIEnumerable

Perché la chiamata .Count () genera l'eccezione?

Non ho .Count()attivato SQL Profiler per confermare, ma sospetto che stia innescando un'altra chiamata a SQL Server e internamente sta riutilizzando lo stesso SQLCommandoggetto e sta tentando di aggiungere nuovamente i parametri duplicati.

Soluzione / Codice di lavoro

Ho aggiunto un contatore all'interno del mio foreach, in modo da poter tenere un conteggio delle righe senza dover chiamare.Count()

int rowCount = 0;

foreach(var row in rows) {
    rowCount++
    // do something
}

if (rowCount == 0) {
    // tell user there are no rows
}

Dopo

Il mio progetto probabilmente utilizza una vecchia versione di EF. La versione più recente potrebbe aver corretto questo bug interno cancellando i parametri o eliminando l' SqlCommandoggetto.

O forse, ci sono istruzioni esplicite che dicono agli sviluppatori di non chiamare .Count()dopo aver ripetuto a DbRawSqlQuery, e sto codificando male.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.