System.Data.SqlClient.SqlException po CREATE / ALTER / PRINT

Sep 03 2020

Wychodzę z 2 innych pytań i próbuję zrozumieć, dlaczego zdarza się ten wyjątek.

Entity Framework materiał siewny -> SqlException: zresetowanie połączenia skutkuje innym stanem niż początkowe logowanie. Logowanie nie powiodło się. results-in-a-dif

Co oznacza „Resetowanie połączenia”? System.Data.SqlClient.SqlException (0x80131904)

Ten kod odtwarza wyjątek.

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

Pełny ślad stosu to

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()
...

Jeśli zmienię pierwszy Run(dbName...do Run("master"...działa poprawnie. Jest to więc związane z działaniem ALTER DATABASEw kontekście tej samej bazy danych

Co oznacza „Resetowanie połączenia”? Dlaczego sesja jest „w stanie zabicia”. ? Czy powinienem unikać uruchamiania instrukcji „ALTER” w tej samej bazie danych? Czemu?

Odpowiedzi

2 DanGuzman Sep 06 2020 at 17:03

Błąd „Resetowanie połączenia skutkuje innym stanem niż początkowe logowanie. Logowanie nie powiodło się”. wynika z ponownego wykorzystania połączenia w puli po zmianie stanu bazy danych (zmiana sortowania bazy danych). Poniżej opisano, co dzieje się wewnętrznie, co prowadzi do błędu.

Kiedy ten kod działa:

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

ADO.NET szuka istniejącego połączenia w puli, dopasowując parametry połączenia i kontekst zabezpieczeń. Nie znaleziono żadnego, ponieważ ciąg połączenia istniejącego połączenia w puli (z CREATE DATABASEzapytania) jest inny ( masterbaza danych zamiast TESTDB). Następnie ADO.NET tworzy nowe połączenie, które obejmuje ustanowienie połączenia TCP / IP, uwierzytelnianie i inicjalizację sesji programu SQL Server. ALTER DATABASEZapytanie jest uruchamiane na tym nowym związku. Połączenie jest dodawane do puli połączeń, gdy jest usuwane (wychodzi poza usingzakres).

Następnie działa:

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

ADO.NET znajduje istniejące TESTDBpołączenie w puli i używa go zamiast tworzenia wystąpienia nowego połączenia. Gdy PRINTpolecenie jest wysyłane do programu SQL Server, żądanie TDS zawiera flagę resetowania połączenia, aby wskazać, że jest to ponownie używane połączenie w puli. Powoduje to, że SQL Server wewnętrznie wywołuje sp_reset_connectionprace porządkowe, takie jak wycofywanie niezatwierdzonych transakcji, usuwanie tabel tymczasowych, wylogowanie, logowanie itp.), Jak opisano tutaj . Jednak sp_reset_connectionnie można przywrócić połączenia z powrotem do początkowego sortowania z powodu zmiany sortowania bazy danych, co powoduje niepowodzenie logowania.

Poniżej znajduje się kilka technik unikania błędu. Proponuję opcję 3.

  1. wywołaj SqlConnection.ClearAllPools()metodę statyczną po zmianie sortowania

  2. Określ masterzamiast TESTDBdla ALTER DATABASEpolecenia, aby istniejące połączenie puli „nadrzędnej” zostało ponownie użyte zamiast tworzenia nowego połączenia. Kolejne PRINTpolecenie utworzy wówczas nowe połączenie, TESTDBponieważ nie istnieje w puli.

  3. określ sortowanie w CREATE DATABASEinstrukcji i ALTER DATABASEcałkowicie usuń polecenie