SqlParameter zaten başka bir SqlParameterCollection tarafından içeriliyor - () {} kullanmak hile yapıyor mu?


87

using() {}Aşağıda gösterildiği gibi (sic) blokları kullanırken ve cmd1bunun ilk using() {}bloğun kapsamının dışında yaşamadığını varsayarsak , neden ikinci blok mesajla bir istisna atsın?

SqlParameter zaten başka bir SqlParameterCollection tarafından içeriliyor

Bu, SqlParameterCollectioneklenen kaynakların ve / veya tutamaçların - parametreler ( ) dahil - cmd1bloğun sonunda yok edildiğinde serbest bırakılmadığı anlamına mı geliyor ?

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

NOT: cmd1.Parameters.Clear () öğesini ilk kullanan () {} bloğunun son küme ayracından hemen önce yapmak sizi istisnadan (ve olası utançtan) kurtaracaktır.

Yeniden üretmeniz gerekiyorsa, nesneleri oluşturmak için aşağıdaki komut dosyalarını kullanabilirsiniz:

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

Bunu da görüyorum ama bu düzeltme işe yaramadı. Sinir bozucu. Ve yeniden kullanılmayan tek bir cmd nesnesi kullanıyorum. Eşzamansız bir yeniden deneme döngüsüne sarılmıştır, bu nedenle muhtemelen aynı temel neden aynı şekilde önlenmemiştir.
Ed Williams

Yanıtlar:


111

Bunun SqlParameterhangi komutun bir parçası olduğunu "bildiğinden" ve bu bilginin komut verildiğinde temizlenmediğini , ancak aradığınızda temizlendiğinden şüpheleniyorum command.Parameters.Clear().

Şahsen nesneleri yeniden kullanmaktan kaçınacağımı düşünüyorum, ancak bu size kalmış :)


2
Teşekkürler. Durumun bu olduğundan şüphelendim. Aynı zamanda SqlParameter'in kendisini, bunun iyi bir şey olduğundan emin olmadığım, elden çıkarılmış bir nesneyle ilişkilendirdiği anlamına gelir
John Gathogo,

@JohnGathogo: Bu, dernek kurulduktan sonra elden çıkarılan bir nesne ile ilişkili. Kesinlikle ideal değil.
Jon Skeet 2011

11
Başkaları için bir not. Clearİlk usingbloktan çıkmadan önce yapmak zorunda kaldım . 2. usingbloğuma girerken yapmak hala bu hatayı attı.
Snekse

@JonSkeet, sadece başka bir sorgu yapmak için aynı parametre kümesini yeniden oluşturmak zorunda olmak aptalca. sıkı bağlantı gibi görünüyor
symbiont

9

Blokların kullanılması, bir nesnenin "yok edilmesini", basitçe Dispose()yöntemin çağrılmasını sağlamaz . Gerçekte ne yaptığı belirli bir uygulamaya bağlıdır ve bu durumda koleksiyonu açıkça boşaltmaz. Buradaki fikir, çöp toplayıcı tarafından temizlenmeyecek olan yönetilmeyen kaynakların doğru bir şekilde atılmasını sağlamaktır. Parameters koleksiyonu yönetilmeyen bir kaynak olmadığından, tamamen şaşırtıcı değildir, dispose yöntemi ile temizlenmemiştir.


7

Cmd.Parameters.Clear (); yürütmeden sonra iyi olmalı.


3

usingbir kapsam tanımlar ve Dispose()sevdiğimiz otomatik çağrıyı yapar .

Kapsam dışında kalan bir referans, başka bir nesnenin kendisine bir referansı varsa, nesnenin kendisini "ortadan kaldırmaz", bu durumda parametersbir referans olması durumu söz konusu olacaktır cmd1.


2

Ben de aynı sorunu yaşadım Teşekkürler @ Jon, buna dayanarak örnek verdim.

Aşağıdaki fonksiyonu çağırdığımda 2 kere aynı sqlparametresi geçti. İlk veritabanı çağrısında düzgün çağrıldı, ancak ikinci seferde yukarıdaki hata verildi.

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

Bu, ortak DAL işlevidir

       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

Bu özel hatayla karşılaştım çünkü aynı SqlParameter nesnelerini bir prosedürü birden çok kez çağırmak için bir SqlParameter koleksiyonunun parçası olarak kullanıyordum. IMHO bu hatanın nedeni, SqlParameter nesnelerinin belirli bir SqlParameter Koleksiyonuyla ilişkilendirilmesi ve yeni bir SqlParameter koleksiyonu oluşturmak için aynı SqlParameter nesnelerini kullanamamanızdır.

Yani bunun yerine:

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

Bunu yap:

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

Bu istisna ile karşılaştım çünkü bir parametre nesnesini örnekleyememiştim. Aynı adı taşıyan parametrelere sahip iki prosedürden şikayetçi olduğunu düşündüm. Aynı parametrenin iki kez eklenmesinden şikayet ediyordu.

            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

Sorun
Bu sorunla karşılaştığımda C # 'dan bir SQL Server saklı yordamı yürütüyordum:

İstisna mesajı [SqlParameter zaten başka bir SqlParameterCollection tarafından içeriliyor.]

Çünkü
saklı yordamıma 3 parametre geçiriyordum. Ekledim

param = command.CreateParameter();

tamamen bir kez. Her parametre için bu satırı eklemeliydim, toplam 3 kez anlamına geliyor.

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

Çözüm
Her parametre için aşağıdaki kod satırını ekleme

param = command.CreateParameter();

0

Ben böyle yaptım!

        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 kullanıyorsanız

Bende de aynı istisna vardı. Benim durumumda, EntityFramework DBContext aracılığıyla SQL'i çağırıyordum. Aşağıdaki kodum ve onu nasıl düzelttiğimdir.

Kırık Kod

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
}

Not: Yukarıdaki çağrı SqlQuery<T>()aslında bir döndüren DbRawSqlQuery<T>, hangi uygularIEnumerable

.Count () çağrısı neden istisna atıyor?

Onaylamak için SQL Profiler'ı çalıştırmadım, ancak bunun .Count()başka bir SQL Server çağrısını tetiklediğinden şüpheleniyorum ve dahili olarak aynı SQLCommandnesneyi yeniden kullanıyor ve yinelenen parametreleri yeniden eklemeye çalışıyor.

Çözüm / Çalışma Kodu

İçime bir sayaç ekledim foreach, böylece aramak zorunda kalmadan satır sayısını tutabilirim.Count()

int rowCount = 0;

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

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

Sonrasında

Projem muhtemelen eski bir EF sürümünü kullanıyor. Daha yeni sürüm, parametreleri temizleyerek veya SqlCommandnesneyi atarak bu dahili hatayı düzeltmiş olabilir .

Ya da belki geliştiricilere .Count()a'yı yineledikten sonra aramamalarını söyleyen açık talimatlar var DbRawSqlQueryve ben yanlış kodluyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.