Der SqlParameter ist bereits in einer anderen SqlParameterCollection enthalten - Betrügt using () {}?

84

Wenn Sie die using() {}(sic) -Blöcke wie unten gezeigt verwenden und davon ausgehen, dass cmd1sie nicht über den Umfang des ersten using() {}Blocks hinausgehen , warum sollte der zweite Block eine Ausnahme mit der Nachricht auslösen?

Der SqlParameter ist bereits in einer anderen SqlParameterCollection enthalten

Bedeutet dies, dass Ressourcen und / oder Handles - einschließlich der Parameter ( SqlParameterCollection) -, an die sie angehängt cmd1sind, nicht freigegeben werden, wenn sie am Ende des Blocks zerstört werden?

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

HINWEIS: Wenn Sie cmd1.Parameters.Clear () kurz vor der letzten Klammer des ersten using () {} -Blocks ausführen , werden Sie vor der Ausnahme (und möglichen Verlegenheit) bewahrt .

Wenn Sie reproduzieren müssen, können Sie die Objekte mit den folgenden Skripten erstellen:

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
quelle

Antworten:

109

Ich vermute , dass SqlParameter„weiß“ , welche es ist Teil gebiete, und dass diese Informationen nicht gelöscht wird , wenn der Befehl angeordnet, sondern wird gelöscht , wenn Sie anrufen command.Parameters.Clear().

Persönlich denke ich, ich würde es vermeiden, die Objekte überhaupt wiederzuverwenden, aber es liegt an Ihnen :)

Jon Skeet
quelle
1
Vielen Dank. Ich vermutete, dass dies der Fall ist. Es würde auch bedeuten, dass sich der SqlParameter mit einem entsorgten Objekt verbindet, von dem ich nicht sicher bin, ob es eine gute Sache ist
John Gathogo,
@ JohnGathogo: Nun, es ist mit einem Objekt verknüpft, das nach der Bildung der Zuordnung entsorgt wird. Es ist sicherlich nicht ideal.
Jon Skeet
11
Eine Notiz für andere. Ich musste das ausführen, Clearbevor ich den ersten usingBlock verließ. Wenn ich meinen zweiten usingBlock betrete, wird dieser Fehler immer noch ausgelöst.
Snekse
9

Die Verwendung von Blöcken stellt nicht sicher, dass ein Objekt "zerstört" wird, sondern lediglich, dass die Dispose()Methode aufgerufen wird. Was dies tatsächlich tut, hängt von der spezifischen Implementierung ab und in diesem Fall wird die Sammlung eindeutig nicht geleert. Damit soll sichergestellt werden, dass nicht verwaltete Ressourcen, die vom Garbage Collector nicht bereinigt werden, ordnungsgemäß entsorgt werden. Da die Parameters-Auflistung keine nicht verwaltete Ressource ist, ist es nicht ganz überraschend, dass sie nicht durch die dispose-Methode gelöscht wird.

Ben Robinson
quelle
7

Hinzufügen von cmd.Parameters.Clear (); nach der Ausführung sollte in Ordnung sein.

Nish
quelle
3

usingdefiniert einen Bereich und führt den automatischen Aufruf aus, Dispose()für den wir ihn lieben.

Eine Referenz, die außerhalb des Gültigkeitsbereichs liegt, lässt das Objekt selbst nicht "verschwinden", wenn auf ein anderes Objekt eine Referenz verweist, was in diesem Fall der Fall ist, parameterswenn auf eine Referenz verwiesen wird cmd1.

Jon Hanna
quelle
2

Ich habe auch das gleiche Problem Danke @Jon, basierend auf dem, was ich Beispiel gegeben habe.

Als ich die folgende Funktion aufrief, in der 2 mal der gleiche SQL-Parameter übergeben wurde. Beim ersten Datenbankaufruf wurde es ordnungsgemäß aufgerufen, beim zweiten Mal wurde der obige Fehler ausgegeben.

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

Dies ist die übliche DAL-Funktion

       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
quelle
2

Ich bin auf diesen speziellen Fehler gestoßen, weil ich dieselben SqlParameter-Objekte als Teil einer SqlParameter-Auflistung zum mehrmaligen Aufrufen einer Prozedur verwendet habe. Der Grund für diesen Fehler ist meiner Meinung nach, dass die SqlParameter-Objekte einer bestimmten SqlParameter-Sammlung zugeordnet sind und Sie nicht dieselben SqlParameter-Objekte verwenden können, um eine neue SqlParameter-Sammlung zu erstellen.

Also stattdessen:

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

Mach das:

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
quelle
0

Ich bin auf diese Ausnahme gestoßen, weil ich ein Parameterobjekt nicht instanziieren konnte. Ich dachte, es würde sich über zwei Verfahren mit gleichnamigen Parametern beschweren. Es wurde beschwert, dass derselbe Parameter zweimal hinzugefügt wurde.

            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
quelle
0

Geben
Sie mir eine SQL Server gespeicherte Prozedur von C # ausgeführt wird, wenn ich dieses Problem auftreten:

Ausnahmemeldung [Der SqlParameter ist bereits in einer anderen SqlParameterCollection enthalten.]

Ursache
Ich habe 3 Parameter an meine gespeicherte Prozedur übergeben. Ich habe das hinzugefügt

param = command.CreateParameter();

insgesamt nur einmal. Ich hätte diese Zeile für jeden Parameter hinzufügen sollen, es bedeutet insgesamt dreimal.

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

Lösung
Hinzufügen der folgenden Codezeile für jeden Parameter

param = command.CreateParameter();
Goldfisch
quelle
0

So habe ich es gemacht!

        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
quelle
0

Wenn Sie EntityFramework verwenden

Ich hatte auch die gleiche Ausnahme. In meinem Fall habe ich SQL über einen EntityFramework-DBContext aufgerufen. Das Folgende ist mein Code und wie ich ihn repariert habe.

Defekter Code

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
}

Hinweis: Der obige Aufruf von gibt SqlQuery<T>()tatsächlich a zurück DbRawSqlQuery<T>, was implementiert wirdIEnumerable

Warum löst der Aufruf von .Count () die Ausnahme aus?

Ich habe SQL Profiler nicht zur Bestätigung gestartet, aber ich vermute, dass dies einen .Count()weiteren Aufruf von SQL Server auslöst und intern dasselbe SQLCommandObjekt wiederverwendet und versucht, die doppelten Parameter erneut hinzuzufügen.

Lösung / Arbeitscode

Ich fügte einen Zähler in meinem hinzu foreach, damit ich eine Zeilenzählung durchführen konnte, ohne anrufen zu müssen.Count()

int rowCount = 0;

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

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

Trotzdem

Mein Projekt verwendet wahrscheinlich eine alte Version von EF. Die neuere Version hat diesen internen Fehler möglicherweise behoben, indem die Parameter gelöscht oder das SqlCommandObjekt entsorgt wurden .

Oder vielleicht gibt es explizite Anweisungen, die Entwickler anweisen, .Count()nach dem Iterieren von a nicht aufzurufen DbRawSqlQuery, und ich codiere es falsch.

Walter Stabosz
quelle