SqlParameter jest już zawarty w innym SqlParameterCollection - czy użycie () {} oszukuje?

85

Używając using() {}(sic) bloków, jak pokazano poniżej, i zakładając, że cmd1nie wykracza poza zakres pierwszego using() {}bloku, dlaczego drugi blok miałby rzucać wyjątek z komunikatem

SqlParameter jest już zawarty w innym SqlParameterCollection

Czy to oznacza, że ​​zasoby i / lub uchwyty - w tym parametry ( SqlParameterCollection) - do których cmd1są dołączone nie są zwalniane, gdy zostaną zniszczone na końcu bloku?

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

UWAGA: Wykonanie cmd1.Parameters.Clear () tuż przed ostatnim nawiasem klamrowym pierwszego bloku using () {} uchroni cię od wyjątku (i możliwego zażenowania).

Jeśli chcesz odtworzyć, możesz użyć następujących skryptów do tworzenia obiektów:

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
John Gathogo
źródło
Ja też to widzę, ale ta poprawka nie zadziałała. Denerwujący. Używam tylko jednego obiektu cmd, którego nie używam ponownie. Jest zawinięty w pętlę ponawiania asynchronicznego, więc prawdopodobnie jest to ta sama główna przyczyna, ale nie można jej uniknąć w ten sam sposób.
Ed Williams

Odpowiedzi:

109

Podejrzewam, że SqlParameter„wie”, którego polecenia jest częścią, i że informacje te nie są usuwane, gdy polecenie jest usuwane, ale usuwane, gdy wywołujesz command.Parameters.Clear().

Osobiście uważam, że w pierwszej kolejności unikałbym ponownego wykorzystania obiektów, ale to zależy od Ciebie :)

Jon Skeet
źródło
2
Dzięki. Podejrzewałem, że tak jest. Oznaczałoby to również, że parametr SqlParameter kojarzy się z usuniętym obiektem, co do którego nie jestem pewien, że jest dobry
John Gathogo
@JohnGathogo: Cóż, jest powiązany z obiektem, który jest usuwany po utworzeniu powiązania. Z pewnością nie jest idealny.
Jon Skeet
11
Uwaga dla innych. Musiałem wykonać Clearprzed wyjściem z pierwszego usingbloku. Robiąc to przy wejściu do mojego drugiego usingbloku nadal wyrzucałem ten błąd.
Snekse
9

Użycie bloków nie gwarantuje, że obiekt zostanie „zniszczony”, a jedynie Dispose()wywołanie metody. To, co tak naprawdę robi, zależy od konkretnej implementacji iw tym przypadku wyraźnie nie opróżnia kolekcji. Chodzi o to, aby zapewnić, że niezarządzane zasoby, które nie zostaną wyczyszczone przez moduł odśmiecania pamięci, zostaną prawidłowo usunięte. Ponieważ kolekcja Parameters nie jest niezarządzanym zasobem, nie jest to całkowicie zaskakujące, że nie jest czyszczona metodą dispose.

Ben Robinson
źródło
7

Dodanie cmd.Parameters.Clear (); po wykonaniu powinno być dobrze.

Nish
źródło
3

usingdefiniuje zakres i wykonuje automatyczne wywołanie, Dispose()za które go kochamy.

Odniesienie wykraczające poza zakres nie spowoduje, że sam obiekt „zniknie”, jeśli inny obiekt ma do niego odniesienie, co w tym przypadku będzie miało miejsce w przypadku parametersodniesienia do cmd1.

Jon Hanna
źródło
2

Mam również ten sam problem Dzięki @Jon, na podstawie tego podałem przykład.

Kiedy wywołałem poniższą funkcję, w której przeszło 2 razy ten sam parametr sql. W pierwszym wywołaniu bazy danych została wywołana poprawnie, ale za drugim razem wystąpił powyższy błąd.

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

Jest to typowa funkcja 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
źródło
2

Napotkałem ten konkretny błąd, ponieważ korzystałem z tych samych obiektów SqlParameter jako części kolekcji SqlParameter do wielokrotnego wywoływania procedury. Przyczyną tego błędu IMHO jest to, że obiekty SqlParameter są skojarzone z określoną kolekcją SqlParameter i nie można użyć tych samych obiektów SqlParameter do utworzenia nowej kolekcji SqlParameter.

Więc zamiast tego:

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

Zrób to:

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
źródło
0

Napotkałem ten wyjątek, ponieważ nie udało mi się utworzyć wystąpienia obiektu parametru. Myślałem, że narzeka na dwie procedury mające parametry o tej samej nazwie. Narzekał na dwukrotne dodanie tego samego parametru.

            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.
Jon Boy
źródło
0

Problem podczas
wykonywania procedury składowanej programu SQL Server z C #, gdy napotkałem ten problem:

Komunikat o wyjątku [SqlParameter jest już zawarty w innym SqlParameterCollection.]

Ponieważ
przekazywałem 3 parametry do mojej procedury składowanej. Dodałem

param = command.CreateParameter();

tylko raz w sumie. Powinienem był dodać tę linię dla każdego parametru, czyli łącznie 3 razy.

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

Rozwiązanie
Dodanie następującego wiersza kodu dla każdego parametru

param = command.CreateParameter();
Złota Rybka
źródło
0

Tak to zrobiłem!

        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
źródło
0

Jeśli używasz EntityFramework

Miałem też ten sam wyjątek. W moim przypadku wywoływałem SQL za pośrednictwem EntityFramework DBContext. Poniżej znajduje się mój kod i jak go naprawiłem.

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

Uwaga: powyższe wywołanie SqlQuery<T>()faktycznie zwraca a DbRawSqlQuery<T>, który implementujeIEnumerable

Dlaczego wywołanie .Count () zgłasza wyjątek?

Nie uruchomiłem programu SQL Profiler w celu potwierdzenia, ale podejrzewam, że .Count()wyzwala kolejne wywołanie programu SQL Server, a wewnętrznie używa tego samego SQLCommandobiektu i próbuje ponownie dodać zduplikowane parametry.

Rozwiązanie / Kod roboczy

Dodałem licznik wewnątrz mojego foreach, aby móc śledzić liczbę wierszy bez konieczności dzwonienia.Count()

int rowCount = 0;

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

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

Mimo wszystko

Mój projekt prawdopodobnie korzysta ze starej wersji EF. Nowsza wersja mogła naprawić ten wewnętrzny błąd, usuwając parametry lub usuwając SqlCommandobiekt.

A może istnieją wyraźne instrukcje, które mówią programistom, aby nie dzwonili .Count()po iteracji a DbRawSqlQuery, a ja koduję to źle.

Walter Stabosz
źródło