Параметр SqlParameterCollection уже содержится в другой коллекции SqlParameterCollection — Обманывает ли использование() {}?

#c# #.net #sql-server #ado.net

#c# #.net #sql-server #ado.net

Вопрос:

При использовании 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.Очистить() непосредственно перед последней скобкой первого блока 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
  

Комментарии:

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

Ответ №1:

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

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

Комментарии:

1. Спасибо. Я подозревал, что это так. Это также означало бы, что параметр SqlParameter связывает себя с удаляемым объектом, что, я не уверен, хорошо

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

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

Ответ №2:

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

Комментарии:

1. Буквально самое простое, полезное и чистое решение из существующих. Огромное спасибо, приятель.

Ответ №3:

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

Ответ №4:

Я столкнулся с этой конкретной ошибкой, потому что я использовал одни и те же объекты 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);
  

Ответ №5:

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

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

Ответ №6:

У меня также возникла та же проблема, спасибо @Jon, основываясь на том, что я привел пример.

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

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

Ответ №7:

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

             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.
  

Ответ №8:

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

Сообщение об исключении [Параметр SqlParameterCollection уже содержится в другой коллекции 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();
  

Ответ №9:

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

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

Ответ №10:

Если вы используете 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>() фактически возвращает 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 , и я неправильно кодирую это.