SqlParameter đã được chứa bởi một SqlParameterCollection khác - Việc sử dụng () {} có gian lận không?


85

Trong khi sử dụng các khối using() {}(sic) như được hiển thị bên dưới và giả sử rằng khối cmd1đó không nằm ngoài phạm vi của using() {}khối đầu tiên , thì tại sao khối thứ hai lại đưa ra một ngoại lệ với thông báo

SqlParameter đã được chứa bởi một SqlParameterCollection khác

Có nghĩa là các tài nguyên và / hoặc các xử lý - bao gồm các tham số ( SqlParameterCollection) - được gắn vào cmd1không được giải phóng khi nó bị phá hủy ở cuối khối?

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

LƯU Ý: Thực hiện cmd1.Parameters.Clear () ngay trước dấu ngoặc nhọn cuối cùng của khối using () {} đầu tiên sẽ giúp bạn thoát khỏi trường hợp ngoại lệ (và có thể là sự bối rối).

Nếu bạn cần tái tạo, bạn có thể sử dụng các tập lệnh sau để tạo các đối tượng:

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

Tôi cũng thấy điều này, nhưng bản sửa lỗi đó không hoạt động. Bực bội. Và tôi chỉ sử dụng một đối tượng cmd duy nhất, không sử dụng lại. Nó được bao bọc trong một vòng lặp thử lại không đồng bộ, vì vậy nó có thể là cùng một nguyên nhân gốc rễ nhưng không được tránh theo cùng một cách.
Ed Williams

Câu trả lời:


109

Tôi nghi ngờ rằng SqlParameter"biết" nó là một phần của lệnh nào và thông tin đó không bị xóa khi lệnh được xử lý, nhưng sẽ bị xóa khi bạn gọi command.Parameters.Clear().

Cá nhân tôi nghĩ rằng tôi sẽ tránh sử dụng lại các đối tượng ngay từ đầu, nhưng điều đó tùy thuộc vào bạn :)


1
Cảm ơn. Tôi đã nghi ngờ đó là trường hợp. Nó cũng có nghĩa là các SqlParameter được liên kết bản thân với một đối tượng xử lý mà tôi không chắc chắn một điều tốt của nó
John Gathogo

@JohnGathogo: Chà, nó được liên kết với một đối tượng được xử lý sau khi hiệp hội được thành lập. Nó không phải là lý tưởng, chắc chắn.
Jon Skeet

11
Một lưu ý cho những người khác. Tôi phải thực hiện Cleartrước khi thoát khỏi usingkhối đầu tiên . Làm điều đó khi vào usingkhối thứ 2 của tôi vẫn bị lỗi này.
Snekse

9

Việc sử dụng các khối không đảm bảo rằng một đối tượng bị "phá hủy", chỉ đơn giản là Dispose()phương thức được gọi. Những gì thực sự làm được phụ thuộc vào việc triển khai cụ thể và trong trường hợp này rõ ràng nó không làm trống bộ sưu tập. Ý tưởng là để đảm bảo rằng các tài nguyên không được quản lý sẽ không được người thu gom rác dọn sạch sẽ được xử lý đúng cách. Vì bộ sưu tập Tham số không phải là tài nguyên không được quản lý nên không hoàn toàn ngạc nhiên khi nó không bị xóa bằng phương pháp vứt bỏ.


7

Thêm cmd.Parameters.Clear (); sau khi thực hiện sẽ ổn.


3

usingxác định phạm vi và thực hiện lệnh gọi tự động Dispose()mà chúng tôi yêu thích.

Một tham chiếu nằm ngoài phạm vi sẽ không làm cho bản thân đối tượng "biến mất" nếu một đối tượng khác có tham chiếu đến nó, trong trường hợp này sẽ là trường hợp parameterscó tham chiếu tới cmd1.


2

Tôi cũng gặp vấn đề tương tự Cảm ơn @Jon, dựa trên đó tôi đã đưa ra ví dụ.

Khi tôi gọi hàm dưới đây, trong đó 2 lần cùng một sqlparameter đã vượt qua. Trong lần gọi cơ sở dữ liệu đầu tiên, nó được gọi đúng cách, nhưng trong lần thứ hai, nó xuất hiện lỗi ở trên.

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

Đây là hàm DAL phổ biến

       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

Tôi gặp phải lỗi cụ thể này vì tôi đang sử dụng các đối tượng SqlParameter giống như một phần của bộ sưu tập SqlParameter để gọi một thủ tục nhiều lần. Lý do cho lỗi này IMHO là các đối tượng SqlParameter được liên kết với một Bộ sưu tập SqlParameter cụ thể và bạn không thể sử dụng các đối tượng SqlParameter giống nhau để tạo một bộ sưu tập SqlParameter mới.

Vì vậy, thay vì điều này:

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

Làm cái này:

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

Tôi gặp phải ngoại lệ này vì tôi đã không thể khởi tạo một đối tượng tham số. Tôi nghĩ rằng nó đang phàn nàn về hai thủ tục có các tham số có cùng tên. Nó đã phàn nàn về cùng một thông số được thêm hai lần.

            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

Sự cố
Tôi đang thực thi thủ tục được lưu trữ trên SQL Server từ C # khi tôi gặp sự cố này:

Thông báo ngoại lệ [SqlParameter đã được chứa bởi một SqlParameterCollection khác.]


tôi đã chuyển 3 tham số cho thủ tục đã lưu trữ của mình. Tôi đã thêm

param = command.CreateParameter();

chỉ một lần hoàn toàn. Tôi nên thêm dòng này cho mỗi tham số, nó có nghĩa là 3 lần hoàn toàn.

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

Giải pháp
Thêm dòng mã sau cho mỗi tham số

param = command.CreateParameter();

0

Đây là cách tôi đã làm điều đó!

        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

Nếu bạn đang sử dụng EntityFramework

Tôi cũng có ngoại lệ tương tự. Trong trường hợp của tôi, tôi đang gọi SQL qua EntityFramework DBContext. Sau đây là mã của tôi và cách tôi sửa nó.

Mã bị hỏng

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
}

Lưu ý: lệnh gọi trên SqlQuery<T>()thực sự trả về a DbRawSqlQuery<T>, thực hiệnIEnumerable

Tại sao việc gọi .Count () lại ném ra ngoại lệ?

Tôi chưa kích hoạt SQL Profiler để xác nhận, nhưng tôi nghi ngờ điều đó .Count()đang kích hoạt một lệnh gọi khác đến SQL Server và bên trong nó đang sử dụng lại cùng một SQLCommandđối tượng và cố gắng thêm lại các tham số trùng lặp.

Giải pháp / Mã làm việc

Tôi đã thêm một bộ đếm bên trong của mình foreach, để tôi có thể giữ số hàng mà không cần phải gọi.Count()

int rowCount = 0;

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

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

Sau khi

Dự án của tôi có thể đang sử dụng phiên bản cũ của EF. Phiên bản mới hơn có thể đã sửa lỗi nội bộ này bằng cách xóa các thông số hoặc loại bỏ SqlCommandđối tượng.

Hoặc có thể, có những hướng dẫn rõ ràng yêu cầu các nhà phát triển không gọi .Count()sau khi lặp lại a DbRawSqlQueryvà tôi đang viết sai.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.