SqlParameter มีอยู่แล้วโดย SqlParameterCollection อื่น - ใช้ () {} โกงหรือไม่


87

ในขณะที่ใช้using() {}บล็อก (sic) ดังที่แสดงด้านล่างและสมมติว่าcmd1ไม่อยู่เกินขอบเขตของusing() {}บล็อกแรกเหตุใดบล็อกที่สองจึงมีข้อยกเว้นด้วยข้อความ

SqlParameter มีอยู่แล้วโดย SqlParameterCollection อื่น

หมายความว่าทรัพยากรและ / หรือจัดการ - รวมถึงพารามิเตอร์ ( SqlParameterCollection) - ที่แนบมาcmd1จะไม่ถูกปล่อยออกมาเมื่อถูกทำลายที่ส่วนท้ายของบล็อกหรือไม่?

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

หมายเหตุ: การทำ cmd1.Parameters.Clear () ก่อนวงเล็บปีกกาสุดท้ายของการใช้ () {}ครั้งแรกจะช่วยให้คุณรอดจากข้อยกเว้น (และความลำบากใจที่อาจเกิดขึ้น)

หากคุณต้องการสร้างซ้ำคุณสามารถใช้สคริปต์ต่อไปนี้เพื่อสร้างวัตถุ:

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

ฉันเห็นสิ่งนี้เช่นกัน แต่การแก้ไขนั้นไม่ได้ผล น่าผิดหวัง และฉันใช้เพียงวัตถุ cmd เดียวเท่านั้นไม่ได้ใช้ซ้ำ มันห่อด้วย async retry loop ดังนั้นจึงอาจเป็นสาเหตุเดียวกัน แต่ไม่ได้หลีกเลี่ยงในลักษณะเดียวกัน
Ed Williams

คำตอบ:


111

ฉันสงสัยว่าSqlParameter"รู้" คำสั่งที่เป็นส่วนหนึ่งของและว่าข้อมูลที่ไม่ได้ล้างออกเมื่อคำสั่งจำหน่าย แต่ถูกcommand.Parameters.Clear()ล้างออกเมื่อคุณเรียก

โดยส่วนตัวแล้วฉันคิดว่าฉันจะหลีกเลี่ยงการนำวัตถุกลับมาใช้ใหม่ในตอนแรก แต่ก็ขึ้นอยู่กับคุณ :)


2
ขอบคุณ. ฉันสงสัยว่าเป็นอย่างนั้น นอกจากนี้ยังจะหมายถึง SqlParameter จะเชื่อมโยงตัวเองกับวัตถุที่จำหน่ายไปซึ่งผมไม่แน่ใจว่าเป็นสิ่งที่ดี
จอห์น Gathogo

@JohnGathogo: มันเกี่ยวข้องกับวัตถุที่ถูกกำจัดหลังจากที่มีการเชื่อมโยง มันไม่เหมาะอย่างแน่นอน
Jon Skeet

11
หมายเหตุสำหรับคนอื่น ๆ ฉันต้องดำเนินการClearก่อนที่จะออกจากusingบล็อกแรก เมื่อเข้าสู่usingบล็อกที่2 ของฉันยังคงแสดงข้อผิดพลาดนี้
Snekse

@JonSkeet เป็นเรื่องงี่เง่าที่ต้องสร้างชุดพารามิเตอร์เดียวกันขึ้นมาใหม่เพียงแค่ทำการสืบค้น ดูเหมือนว่าการมีเพศสัมพันธ์ที่แน่นหนา
symbiont

9

การใช้บล็อกไม่ให้แน่ใจว่าวัตถุถูก "ทำลาย" เพียงแค่Dispose()เรียกวิธีการนั้น สิ่งที่ทำได้จริงขึ้นอยู่กับการนำไปใช้งานเฉพาะและในกรณีนี้จะไม่ทำให้คอลเล็กชันว่างเปล่าอย่างชัดเจน แนวคิดคือเพื่อให้แน่ใจว่าทรัพยากรที่ไม่มีการจัดการซึ่งตัวรวบรวมขยะจะไม่ถูกกำจัดอย่างถูกต้อง เนื่องจากคอลเล็กชันพารามิเตอร์ไม่ใช่ทรัพยากรที่ไม่มีการจัดการจึงไม่น่าแปลกใจเลยที่ไม่มีการล้างด้วยวิธีการกำจัด



3

usingกำหนดขอบเขตและเรียกDispose()สิ่งที่เราชอบโดยอัตโนมัติ

อ้างอิงล้มออกจากขอบเขตจะไม่ทำให้วัตถุเอง "หาย" ถ้าวัตถุอื่นมีการอ้างอิงไปซึ่งในกรณีนี้จะเป็นกรณีสำหรับการมีการอ้างอิงถึงparameterscmd1


2

ฉันมีปัญหาเดียวกันด้วยขอบคุณ @Jon ตามที่ฉันยกตัวอย่าง

เมื่อฉันเรียกใช้ฟังก์ชันด้านล่างซึ่ง sqlparameter 2 เท่าผ่านไป ในการเรียกฐานข้อมูลครั้งแรกมันถูกเรียกอย่างถูกต้อง แต่ในครั้งที่สองจะมีข้อผิดพลาดข้างต้น

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

นี่คือฟังก์ชัน 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;
        }

2

ฉันพบข้อผิดพลาดนี้เนื่องจากฉันใช้อ็อบเจ็กต์ SqlParameter เดียวกันเป็นส่วนหนึ่งของคอลเล็กชัน SqlParameter สำหรับการเรียกโพรซีเดอร์หลาย ๆ ครั้ง สาเหตุของข้อผิดพลาด IMHO นี้คืออ็อบเจ็กต์ SqlParameter เชื่อมโยงกับ SqlParameter Collection เฉพาะและคุณไม่สามารถใช้อ็อบเจ็กต์ SqlParameter เดียวกันเพื่อสร้างคอลเล็กชัน SqlParameter ใหม่

ดังนั้นแทนที่จะเป็นสิ่งนี้:

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

ทำเช่นนี้:

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

ฉันพบข้อยกเว้นนี้เนื่องจากฉันไม่สามารถสร้างอินสแตนซ์ออบเจ็กต์พารามิเตอร์ได้ ฉันคิดว่ามันกำลังบ่นเกี่ยวกับสองขั้นตอนที่มีพารามิเตอร์ที่มีชื่อเดียวกัน มีการบ่นเกี่ยวกับการเพิ่มพารามิเตอร์เดียวกันสองครั้ง

            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

ปัญหา
ฉันกำลังเรียกใช้กระบวนงานที่เก็บ SQL Server จาก C # เมื่อฉันพบปัญหานี้:

ข้อความข้อยกเว้น [SqlParameter มีอยู่แล้วโดย SqlParameterCollection อื่น]

สาเหตุที่
ฉันส่งพารามิเตอร์ 3 ตัวไปยังขั้นตอนการจัดเก็บของฉัน ฉันเพิ่มไฟล์

param = command.CreateParameter();

เพียงครั้งเดียวทั้งหมด ฉันควรจะเพิ่มบรรทัดนี้สำหรับแต่ละพารามิเตอร์มันหมายถึง 3 ครั้งด้วยกัน

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

วิธีแก้ไขการ
เพิ่มบรรทัดของโค้ดต่อไปนี้สำหรับแต่ละพารามิเตอร์

param = command.CreateParameter();

0

นี่คือวิธีที่ฉันได้ทำ!

        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

ฉันยังมีข้อยกเว้นเดียวกันนี้ ในกรณีของฉันฉันกำลังเรียก SQL ผ่าน EntityFramework DBContext ต่อไปนี้เป็นรหัสของฉันและจะแก้ไขอย่างไร

รหัสเสีย

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
}

หมายเหตุ:การเรียกใช้ข้างต้นเพื่อSqlQuery<T>()ส่งคืน a จริงDbRawSqlQuery<T>ซึ่งดำเนินการIEnumerable

เหตุใดการโทร. นับ () จึงทำให้เกิดข้อยกเว้น

ฉันไม่ได้เปิดใช้งาน SQL Profiler เพื่อยืนยัน แต่ฉันสงสัยว่า.Count()กำลังเรียกใช้การเรียกใช้ SQL Server อีกครั้งและภายในกำลังใช้SQLCommandวัตถุเดียวกันซ้ำและพยายามเพิ่มพารามิเตอร์ที่ซ้ำกันอีกครั้ง

โซลูชัน / รหัสการทำงาน

ฉันได้เพิ่มตัวนับเข้าไปในตัวforeachเพื่อที่ฉันจะได้นับแถวได้โดยไม่ต้องโทร.Count()

int rowCount = 0;

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

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

หลังจากนั้น

โครงการของฉันอาจใช้ EF เวอร์ชันเก่า เวอร์ชันที่ใหม่กว่าอาจแก้ไขข้อบกพร่องภายในนี้โดยการล้างพารามิเตอร์หรือกำจัดSqlCommandออบเจ็กต์

หรืออาจจะมีคำแนะนำอย่างชัดเจนที่บอกให้นักพัฒนาไม่ต้องโทร.Count()หลังจากทำซ้ำ a DbRawSqlQueryและฉันเขียนโค้ดผิด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.