El SqlParameter ya está contenido en otro SqlParameterCollection. ¿Es engañoso using () {}?

85

Mientras usa los using() {}bloques (sic) como se muestra a continuación, y asumiendo que cmd1no vive más allá del alcance del primer using() {}bloque, ¿por qué el segundo bloque debería lanzar una excepción con el mensaje?

El SqlParameter ya está contenido en otro SqlParameterCollection

¿Significa que los recursos y / o identificadores, incluidos los parámetros ( SqlParameterCollection), adjuntos cmd1no se liberan cuando se destruyen al final del bloque?

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: Hacer cmd1.Parameters.Clear () justo antes de la última llave del primer bloque using () {} lo salvará de la excepción (y posible vergüenza).

Si necesita reproducir, puede utilizar los siguientes scripts para crear los objetos:

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
John Gathogo
fuente
También veo esto, pero esa solución no funcionó. Frustrante. Y solo estoy usando un único objeto cmd, no reutilizado. Está envuelto en un bucle de reintento asincrónico, por lo que probablemente sea la misma causa raíz, pero no se evita de la misma manera.
Ed Williams hace

Respuestas:

109

Sospecho que SqlParameter"sabe" de qué comando forma parte y que esa información no se borra cuando se elimina el comando, pero se borra cuando se llama command.Parameters.Clear().

Personalmente, creo que evitaría reutilizar los objetos en primer lugar, pero depende de ti :)

Jon Skeet
fuente
1
Gracias. Sospeché que ese es el caso. También significaría que el SqlParameter se está asociando con un objeto desechado que no estoy seguro de que sea algo bueno
John Gathogo
@JohnGathogo: Bueno, está asociado con un objeto que se elimina después de que se ha formado la asociación. Ciertamente no es lo ideal.
Jon Skeet
11
Una nota para los demás. Tuve que realizar el Clearantes de salir del primer usingbloque. Hacerlo al ingresar a mi segundo usingbloque todavía arrojaba este error.
Snekse
9

El uso de bloques no asegura que un objeto sea "destruido", simplemente que Dispose()se llama al método. Lo que realmente hace depende de la implementación específica y, en este caso, claramente no vacía la colección. La idea es garantizar que los recursos no administrados que no serían limpiados por el recolector de basura se eliminen correctamente. Como la colección Parameters no es un recurso no administrado, no es del todo sorprendente que no se borre con el método dispose.

Ben Robinson
fuente
7

Añadiendo cmd.Parameters.Clear (); después de la ejecución debería estar bien.

Nish
fuente
3

usingdefine un alcance y realiza la llamada automática de Dispose()para lo que nos encanta.

Una referencia que se salga del alcance no hará que el objeto en sí "desaparezca" si otro objeto tiene una referencia a él, que en este caso será el caso de parameterstener una referencia cmd1.

Jon Hanna
fuente
2

También tengo el mismo problema Gracias @Jon, en base a eso di un ejemplo.

Cuando llamé a la siguiente función en la que pasó 2 veces el mismo parámetro sql. En la primera llamada a la base de datos, se llamó correctamente, pero en la segunda vez, se dio el error anterior.

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

Esta es la función DAL común

       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;
        }
Ajay2707
fuente
2

Enfrenté este error en particular porque estaba usando los mismos objetos SqlParameter como parte de una colección SqlParameter para llamar a un procedimiento varias veces. En mi humilde opinión, la razón de este error es que los objetos SqlParameter están asociados a una colección SqlParameter particular y no puede usar los mismos objetos SqlParameter para crear una nueva colección SqlParameter.

Entonces, en lugar de esto:

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

Hacer esto:

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);
SaCh
fuente
0

Encontré esta excepción porque no pude crear una instancia de un objeto de parámetro. Pensé que se estaba quejando de que dos procedimientos tenían parámetros con el mismo nombre. Se quejaba de que se agregaba dos veces el mismo parámetro.

            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.
Jon Boy
fuente
0

Problema
Estaba ejecutando un procedimiento almacenado de SQL Server desde C # cuando encontré este problema:

Mensaje de excepción [El SqlParameter ya está incluido en otro SqlParameterCollection.]

Porque
estaba pasando 3 parámetros a mi procedimiento almacenado. Agregué el

param = command.CreateParameter();

solo una vez en total. Debería haber agregado esta línea para cada parámetro, significa 3 veces en total.

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

Solución
Agregar la siguiente línea de código para cada parámetro

param = command.CreateParameter();
Pez de colores
fuente
0

¡Así es como lo he hecho!

        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));
        }
KrazKjn
fuente
0

Si está utilizando EntityFramework

También tuve esta misma excepción. En mi caso, estaba llamando a SQL a través de EntityFramework DBContext. El siguiente es mi código y cómo lo arreglé.

Código roto

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 llamada anterior a en SqlQuery<T>()realidad devuelve a DbRawSqlQuery<T>, que implementaIEnumerable

¿Por qué llamar a .Count () lanza la excepción?

No he .Count()activado SQL Profiler para confirmar, pero sospecho que está activando otra llamada a SQL Server, e internamente está reutilizando el mismoSQLCommand objeto y tratando de volver a agregar los parámetros duplicados.

Solución / Código de trabajo

Agregué un contador dentro de mi foreach, para poder mantener un recuento de filas sin tener que llamar.Count()

int rowCount = 0;

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

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

En el futuro

Probablemente mi proyecto esté usando una versión anterior de EF. Es posible que la versión más nueva haya solucionado este error interno borrando los parámetros o eliminando elSqlCommand objeto.

O tal vez, hay instrucciones explícitas que les dicen a los desarrolladores que no llamen .Count()después de iterar a DbRawSqlQuery, y lo estoy codificando mal.

Walter Stabosz
fuente