Używając using() {}
(sic) bloków, jak pokazano poniżej, i zakładając, że cmd1
nie 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 cmd1
są 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
c#
.net
sql-server
ado.net
John Gathogo
źródło
źródło
Odpowiedzi:
Podejrzewam, że
SqlParameter
„wie”, którego polecenia jest częścią, i że informacje te nie są usuwane, gdy polecenie jest usuwane, ale są usuwane, gdy wywołujeszcommand.Parameters.Clear()
.Osobiście uważam, że w pierwszej kolejności unikałbym ponownego wykorzystania obiektów, ale to zależy od Ciebie :)
źródło
Clear
przed wyjściem z pierwszegousing
bloku. Robiąc to przy wejściu do mojego drugiegousing
bloku nadal wyrzucałem ten błąd.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.źródło
Dodanie cmd.Parameters.Clear (); po wykonaniu powinno być dobrze.
źródło
using
definiuje 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
parameters
odniesienia docmd1
.źródło
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; }
źródło
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);
źródło
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.
źródło
Problem podczas
wykonywania procedury składowanej programu SQL Server z C #, gdy napotkałem ten problem:
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();
źródło
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)); }
źródło
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 aDbRawSqlQuery<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 samegoSQLCommand
obiektu 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
SqlCommand
obiekt.A może istnieją wyraźne instrukcje, które mówią programistom, aby nie dzwonili
.Count()
po iteracji aDbRawSqlQuery
, a ja koduję to źle.źródło