SqlParameter уже содержится в другой коллекции SqlParameterCollection - обманывает ли using () {}?


87

При использовании using() {}(sic) блоков, как показано ниже, и при условии, что cmd1они не выходят за рамки первого using() {}блока, почему второй блок должен генерировать исключение с сообщением

SqlParameter уже содержится в другой коллекции SqlParameterCollection.

Означает ли это, что ресурсы и / или дескрипторы, включая параметры ( SqlParameterCollection), прикрепленные к cmd1, не освобождаются, когда они уничтожаются в конце блока?

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

ПРИМЕЧАНИЕ. Выполнение cmd1.Parameters.Clear () непосредственно перед последней фигурной скобкой первого блока using () {} избавит вас от исключения (и возможного затруднения).

Если вам нужно воспроизвести, вы можете использовать следующие скрипты для создания объектов:

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

Я тоже это вижу, но это не помогло. Разочарование. И я использую только один объект cmd, а не повторно. Он заключен в асинхронный цикл повтора, поэтому, вероятно, это та же основная причина, но ее нельзя было избежать таким же образом.
— Эд Уильямс

Ответы:


111

Я подозреваю , что SqlParameter«знает» , какая команда это часть, и что эта информация не удаляется , когда команда расположена, но это очищается , когда вы звоните command.Parameters.Clear().

Лично я думаю, что я бы вообще избегал повторного использования объектов, но решать вам :)


2
Благодарю. Я подозревал, что это так. Это также означало бы, что SqlParameter связывает себя с удаленным объектом, что я не уверен, что это хорошо
— Джон Гатого

@JohnGathogo: Ну, он связан с объектом, который удаляется после того, как ассоциация была сформирована. Это, конечно, не идеально.
— Джон Скит,

11
Примечание для других. Мне пришлось выполнить это Clearперед выходом из первого usingблока. Выполнение этого при входе во второй usingблок по-прежнему вызывает эту ошибку.
— Снексе

@JonSkeet - это идиотизм - воссоздавать тот же набор параметров, просто чтобы выполнить другой запрос. похоже на тугую связь
— симбионт

9

Использование блоков не гарантирует, что объект «уничтожен», а просто Dispose()вызывает метод. Что это на самом деле делает, зависит от конкретной реализации, и в этом случае он явно не очищает коллекцию. Идея состоит в том, чтобы обеспечить правильное удаление неуправляемых ресурсов, которые не будут очищены сборщиком мусора. Поскольку коллекция Parameters не является неуправляемым ресурсом, неудивительно, что она не очищается методом dispose.


7

Добавление cmd.Parameters.Clear (); после казни должно быть все в порядке.


3

usingопределяет область действия и выполняет автоматический вызов, Dispose()за который мы ее любим.

Ссылка, выпадающая из области видимости, не приведет к «исчезновению» самого объекта, если на него есть ссылка на другой объект, что в данном случае будет иметь место при parametersналичии ссылки cmd1.


2

У меня тоже такая же проблема. Спасибо @Jon, на основе этого я привел пример.

Когда я вызвал функцию ниже, в которой 2 раза прошел один и тот же параметр sql. При первом вызове базы данных он был вызван правильно, но во второй раз возникла указанная выше ошибка.

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

Это обычная функция DAL

       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

Я столкнулся с этой конкретной ошибкой, потому что использовал одни и те же объекты SqlParameter как часть коллекции SqlParameter для многократного вызова процедуры. Причина этой ошибки IMHO заключается в том, что объекты SqlParameter связаны с определенной коллекцией SqlParameter, и вы не можете использовать те же объекты SqlParameter для создания новой коллекции SqlParameter.

Итак, вместо этого:

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

Сделай это:

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

Я столкнулся с этим исключением, потому что мне не удалось создать экземпляр объекта параметра. Я думал, что он жаловался на две процедуры с одинаковыми параметрами. Он жаловался на добавление одного и того же параметра дважды.

            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

Проблема.
Когда я столкнулся с этой проблемой, я выполнял хранимую процедуру SQL Server из C #:

Сообщение об исключении [SqlParameter уже содержится в другой коллекции SqlParameterCollection.]

Потому что
я передавал в свою хранимую процедуру 3 параметра. Я добавил

param = command.CreateParameter();

только один раз вместе. Я должен был добавить эту строку для каждого параметра, то есть всего 3 раза.

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

Решение
Добавление следующей строки кода для каждого параметра

param = command.CreateParameter();

0

Вот как я это сделал!

        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

Если вы используете EntityFramework

У меня тоже было такое же исключение. В моем случае я вызывал SQL через EntityFramework DBContext. Ниже приведен мой код и то, как я его исправил.

Сломанный код

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
}

Примечание: приведенный выше вызов SqlQuery<T>()фактически возвращает a DbRawSqlQuery<T>, который реализуетIEnumerable

Почему вызов .Count () вызывает исключение?

Я не запускал SQL Profiler для подтверждения, но подозреваю, что .Count()это вызывает еще один вызов SQL Server, а внутри он повторно использует тот же SQLCommandобъект и пытается повторно добавить повторяющиеся параметры.

Решение / Рабочий код

Я добавил счетчик в свой foreach, чтобы я мог вести подсчет строк без вызова.Count()

int rowCount = 0;

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

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

Запоздало

В моем проекте, вероятно, используется старая версия EF. В новой версии эта внутренняя ошибка могла быть исправлена ​​путем очистки параметров или удаления SqlCommandобъекта.

Или, может быть, есть явные инструкции, которые говорят разработчикам не вызывать .Count()после итерации DbRawSqlQuery, а я кодирую это неправильно.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.