System.Data.SqlClient.SqlException หลังจาก CREATE / ALTER / PRINT

Sep 03 2020

ฉันมาจากคำถามอื่นอีก 2 ข้อและกำลังพยายามทำความเข้าใจว่าเหตุใดจึงเกิดข้อยกเว้นนี้ขึ้น

Entity Framework seed -> SqlException: การรีเซ็ตการเชื่อมต่อส่งผลให้อยู่ในสถานะที่แตกต่างจากการล็อกอินเริ่มต้น การเข้าสู่ระบบล้มเหลว ผลลัพธ์ใน a-dif

"การรีเซ็ตการเชื่อมต่อ" หมายความว่าอย่างไร System.Data.SqlClient.SqlException (0x80131904)

รหัสนี้ทำให้เกิดข้อยกเว้น

string dbName = "TESTDB";
Run("master", $"CREATE DATABASE [{dbName}]"); Run(dbName, $"ALTER DATABASE [{dbName}] COLLATE Latin1_General_100_CI_AS");
Run(dbName, "PRINT 'HELLO'");

void Run(string catalog, string script)
{
    var cnxStr = new SqlConnectionStringBuilder
    {
        DataSource = serverAndInstance,
        UserID = user,
        Password = password,
        InitialCatalog = catalog
    };

    using var cn = new SqlConnection(cnxStr.ToString());
    using var cm = cn.CreateCommand();
    cn.Open();
    cm.CommandText = script;
    cm.ExecuteNonQuery();
}

stacktrace แบบเต็มคือ

Unhandled Exception: System.Data.SqlClient.SqlException: Resetting the connection results in a different state than the initial login. The login fails.
Login failed for user 'user'.
Cannot continue the execution because the session is in the kill state.
A severe error occurred on the current command.  The results, if any, should be discarded.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
...

ถ้าฉันเปลี่ยนคนแรกที่Run(dbName...ไปRun("master"...มันจะทำงานได้ดี ดังนั้นจึงเกี่ยวข้องกับการทำงานALTER DATABASEในบริบทของฐานข้อมูลเดียวกัน

"การรีเซ็ตการเชื่อมต่อ" หมายความว่าอย่างไร เหตุใดเซสชันจึง "อยู่ในสถานะฆ่า" เหรอ? ฉันควรหลีกเลี่ยงการเรียกใช้คำสั่ง "ALTER" ในฐานข้อมูลเดียวกันหรือไม่ ทำไม?

คำตอบ

2 DanGuzman Sep 06 2020 at 17:03

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

เมื่อรหัสนี้ทำงาน:

Run(dbName, $"ALTER DATABASE [{dbName}] COLLATE Latin1_General_100_CI_AS");

ADO.NET ค้นหาการเชื่อมต่อแบบรวมที่มีอยู่โดยการจับคู่สตริงการเชื่อมต่อและบริบทความปลอดภัย ไม่พบเนื่องจากสตริงการเชื่อมต่อของการเชื่อมต่อแบบรวมที่มีอยู่ (จากCREATE DATABASEคิวรี) แตกต่างกัน ( masterฐานข้อมูลแทนTESTDB) ADO.NET จะสร้างการเชื่อมต่อใหม่ซึ่งรวมถึงการสร้างการเชื่อมต่อ TCP / IP การรับรองความถูกต้องและการเริ่มต้นเซสชัน SQL Server ALTER DATABASEแบบสอบถามมีการเรียกใช้ในการเชื่อมต่อใหม่นี้ การเชื่อมต่อจะถูกเพิ่มไปยังพูลการเชื่อมต่อเมื่อถูกกำจัด (ออกไปนอกusingขอบเขต)

จากนั้นสิ่งนี้จะทำงาน:

Run(dbName, "PRINT 'HELLO'");

ADO.NET ค้นหาการTESTDBเชื่อมต่อรวมที่มีอยู่และใช้สิ่งนั้นแทนการสร้างอินสแตนซ์การเชื่อมต่อใหม่ เมื่อPRINTคำสั่งถูกส่งไปยัง SQL Server คำขอ TDS จะรวมแฟล็กการเชื่อมต่อที่รีเซ็ตเพื่อระบุว่าเป็นการเชื่อมต่อแบบรวมที่ใช้ซ้ำ นี่เป็นสาเหตุ SQL Server เพื่อเรียกภายในsp_reset_connectionจะทำผลงานการทำความสะอาดเช่นการทำธุรกรรมปราศจากข้อผูกมัดย้อนกลับลดลงตารางชั่วคราวออกจากระบบเข้าสู่ระบบ ฯลฯ ) ตามรายละเอียดที่นี่ อย่างไรก็ตามsp_reset_connectionไม่สามารถเปลี่ยนการเชื่อมต่อกลับเป็นการเปรียบเทียบเริ่มต้นได้เนื่องจากการเปลี่ยนแปลงการจัดเรียงฐานข้อมูลส่งผลให้การเข้าสู่ระบบล้มเหลว

ด้านล่างนี้คือเทคนิคบางประการเพื่อหลีกเลี่ยงข้อผิดพลาด ฉันขอแนะนำตัวเลือกที่ 3

  1. เรียกใช้SqlConnection.ClearAllPools()วิธีการแบบคงที่หลังจากเปลี่ยนการจัดเรียง

  2. ระบุmasterแทนTESTDBสำหรับALTER DATABASEคำสั่งเพื่อให้มีอยู่ 'ต้นแบบ' การเชื่อมต่อ pooled ถูกนำกลับมาใช้แทนการสร้างการเชื่อมต่อใหม่ PRINTคำสั่งที่ตามมาจะสร้างการเชื่อมต่อใหม่TESTDBเนื่องจากไม่มีอยู่ในพูล

  3. ระบุการเปรียบเทียบในCREATE DATABASEคำสั่งและลบALTER DATABASEคำสั่งทั้งหมด