Параметр SqlParameter вже міститься в іншому SqlParameterCollection - Чи використовує () {} шахрайство?


85

Використовуючи 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 () безпосередньо перед останньою фігурною дужкою першого блоку () {} позбавить вас від винятку (і можливого збентеження).

Якщо вам потрібно відтворити, ви можете використовувати такі сценарії для створення об'єктів:

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, а не повторно використаний. Він загорнутий в асинхронний цикл повторної спроби, тому, ймовірно, це та сама основна причина, яку не уникнути таким же чином.
Ед Вільямс,

Відповіді:


109

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

Особисто я думаю, що спочатку уникав би повторного використання предметів, але це залежить від вас :)


2
Дякую. Я підозрював, що це так. Це також означало б, що параметр SqlParameter асоціюється з утилізованим об'єктом, що я не впевнений, що це добре
Джон Гатого

@JohnGathogo: Ну, це пов'язано з об'єктом, який утилізується після утворення асоціації. Це, звичайно, не ідеально.
Джон Скіт,

11
Примітка для інших. Мені довелося виконати програму Clearперед виходом з першого usingблоку. Виконуючи це під час введення мого 2-го usingблоку, все-таки виникла ця помилка.
Снексе

9

Використання блоків не гарантує, що об'єкт "знищений", просто Dispose()викликається метод. Те, що насправді робить, залежить від конкретної реалізації, і в цьому випадку це явно не спорожнює колекцію. Ідея полягає в тому, щоб забезпечити належне розпорядження некерованими ресурсами, які не збираються збирачем сміття. Оскільки колекція параметрів не є некерованим ресурсом, вона не зовсім дивовижна, вона не очищується методом 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()після ітерації a DbRawSqlQuery, і я неправильно кодую це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.