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;
        }
Ajay2707
источник
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);
SaCh
источник
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));
        }
KrazKjn
источник
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, а я кодирую это неправильно.

Вальтер Стабош
источник