System.Data.SqlClient.SqlException после CREATE/ALTER/PRINT

Я исхожу из двух других вопросов и пытаюсь понять, почему происходит это исключение.

Начальное значение Entity Framework -> SqlException: сброс соединения приводит к состоянию, отличному от первоначальный логин. Ошибка входа в систему. results-in-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();
}

Полная трассировка стека

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 внутри одной и той же базы данных? Почему?


person pitermarx    schedule 03.09.2020    source источник
comment
Я запустил ваш код (со встроенной безопасностью), и при попытке печати возникает исключение. Я бы предположил, что печать не является частью вашего реального кода, не так ли? Вы проверяли вход пользователя?   -  person insane_developer    schedule 03.09.2020
comment
В сетевой библиотеке есть пул соединений, и вы должны избавиться от пула, иначе вы не сможете изменить объект, который уже используется.   -  person jdweng    schedule 03.09.2020
comment
Вы можете попробовать SqlConnection.ClearAllPools(); после изменения базы данных.   -  person Dan Guzman    schedule 03.09.2020
comment
@jdweng не могли бы вы лучше объяснить, какой объект используется?   -  person pitermarx    schedule 03.09.2020
comment
@DanGuzman Если я очищаю пулы после ALTER, код выполняется без ошибок. Я все же хотел бы понять, почему.   -  person pitermarx    schedule 03.09.2020
comment
См.: docs.microsoft.com/ en-us/dotnet/framework/data/adonet/   -  person jdweng    schedule 03.09.2020
comment
Соединения @jdweng объединяются по строке соединения. В этом случае строка подключения отличается. Пул не должен был пытаться повторно использовать предыдущее соединение. Во всяком случае, это должен привести к слишком большому количеству подключений и фрагментации. Здесь происходит что-то подозрительное   -  person Panagiotis Kanavos    schedule 04.09.2020
comment
Похоже, что запуск ALTER DATABASE со строкой подключения к этой базе данных приводит к тому, что SqlConnection переходит в плохое состояние. Поскольку оно объединено в пул, следующее использование этого соединения завершится ошибкой.   -  person pitermarx    schedule 04.09.2020
comment
При изменении базы данных в SSMS я должен закрыть SSMS и снова открыть, прежде чем код С# повторно колонизирует изменения. Поэтому я бы предположил, что вам придется делать то же самое, когда С# выполняет те же команды.   -  person jdweng    schedule 04.09.2020
comment
@jdweng есть идеи, как это сделать?   -  person pitermarx    schedule 04.09.2020
comment
Закрыть/открыть СМС. Другого способа использования SSMS не нашел. Предположим, то же самое с С#.   -  person jdweng    schedule 04.09.2020
comment
Я могу что-то упустить... Я не использую SSMS, только .NET. Как я могу выполнить операцию, аналогичную закрытию SSMS на С#?   -  person pitermarx    schedule 04.09.2020
comment
Я не уверен, что вы можете запустить PRINT с ado.net. Но если вы просто хотите напечатать сообщение, почему бы вам не использовать Console.WriteLine?   -  person Felipe Oriani    schedule 04.09.2020
comment
Я не хочу печатать. Это просто пример. Любой запрос не работает.   -  person pitermarx    schedule 05.09.2020


Ответы (1)


Ошибка Сброс соединения приводит к состоянию, отличному от исходного входа в систему. Логин не работает. связано с соединением в пуле повторно используется после изменения состояния базы данных (изменения сортировки базы данных). Ниже показано, что происходит внутри, что приводит к ошибке.

Когда этот код работает:

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, чтобы повторно использовать существующее 'главное' объединенное соединение вместо создания нового соединения. Последующая команда PRINT затем создаст новое соединение для TESTDB, так как его нет в пуле.

  3. укажите параметры сортировки в операторе CREATE DATABASE и полностью удалите команду ALTER DATABASE

person Dan Guzman    schedule 06.09.2020