Сбой триггера SQL при вызове хранимой процедуры, использующей CLR

У меня есть две базы данных на одном экземпляре SQL Server.

В «DatabaseA» у меня есть хранимая процедура, которая вызывает код C# (сборка CLR в SQL) для распаковки данных и после распаковки записывает распакованные данные в таблицы DatabaseB (ScanTracking, Strings, Numerics)

Хранимая процедура при выполнении вручную работает нормально.

Однако, когда я запускаю хранимую процедуру через триггер для таблицы, присутствующей в DatabaseA. Я получаю следующую ошибку

«Контекст транзакции используется другим сеансом»

Полная информация об ошибке выглядит следующим образом:

Msg 6522, Level 16, State 1, Procedure ExecuteDeSerializeBulk, Line 0
A .NET Framework error occurred during execution of user-defined routine or aggregate "ExecuteDeSerializeBulk": 
System.Data.SqlClient.SqlException: Transaction context in use by another session.
System.Data.SqlClient.SqlException: 
   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.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
   at System.Data.SqlClient.SqlInternalConnectionTds.PropagateTransactionCookie(Byte[] cookie)
   at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
   at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.PrepareConnection(DbConnection owningObject, DbConnectionInternal obj, Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnecti...
The statement has been terminated.

Я провел небольшое исследование и нашел следующие ссылки

Контекст транзакции, используемый другим сеансом

https://learningintheopen.org/2014/01/21/technical-microsoft-sql-server-transact-sql-linked-server-error-transaction-context-in-use-by-other-session-msg-3910-level-16-state-2-line-1/

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

В качестве обходного пути я вернулся к заданию агента SQL, которое запускается каждые 5 минут и распаковывает данные в базу данных. Это работает нормально. Однако мой разум говорит мне, что я не нахожу решения проблемы с триггером и не работаю над этим, используя задание агента SQL.

Любые мысли или предложения очень помогут.


person Abe    schedule 01.12.2017    source источник
comment
Включен ли сеанс, вызывающий триггер MARS? Вы используете TransactionScope?   -  person Dan Guzman    schedule 01.12.2017
comment
@mjwills - спасибо, я обновил вопрос для вас. Дайте мне знать, можете ли вы теперь что-то понять с полной детализацией исключений. Хорошая аналогия, кстати. Я с нетерпением жду предложения от вас.   -  person Abe    schedule 01.12.2017
comment
Пожалуйста, покажите нам исходный код для ExecuteDeSerializeBulk, триггера и кода C#, который обновляет базу данных (таким образом, «активирует триггер»).   -  person mjwills    schedule 01.12.2017


Ответы (1)


Хранимая процедура при выполнении вручную работает нормально.

Попробуйте сначала выполнить BEGIN TRAN;, а затем запустить хранимую процедуру при ее тестировании. Это ошибка? Он должен получить ту же ошибку, что и при выполнении из триггера. Триггеры выполняются внутри транзакции, инициированной системой, поэтому то, что вы описываете, имеет смысл.

Вот что можно попробовать:

  1. Вместо того, чтобы подключаться к БД для записи чего-либо, верните значения обратно в вызывающий процесс, а затем выполните запись оттуда. Это ваш лучший выбор, если это возможно сделать, поскольку он отделяет зависимость и дает вам больше гибкости в том, как, когда и где использовать код SQLCLR.

  2. Используйте Context Connection = true; для строки подключения

  3. Укажите enlist=false; в строке подключения, чтобы не использовать поведение по умолчанию при попытке присоединения к существующей транзакции.

person Solomon Rutzky    schedule 01.12.2017
comment
Спасибо за ваш комментарий. Я добавил Begin Tran; перед выполнением хранимой процедуры, и я все еще получаю ту же ошибку. Моя CLR возвращает результаты обратно вызову хранимой процедуры sql, а затем хранимая процедура записывает обратно в DatabaseB. - person Abe; 01.12.2017
comment
@Abe Да, при запуске теста самого процесса, который не получал ошибку, вы должны начать получать ошибку при использовании BEGIN TRAN, поскольку он имитирует то, что происходит при запуске этого процесса из триггера. И даже если вы уже делаете № 1 (неясно из описания в вопросе), то как быть с пунктами 2 и 3? Вы не показали в вопросе, как именно вы подключаетесь. Сообщение об ошибке указывает на внешнее соединение, которое пытается закрепиться в текущей транзакции. Выполнение 2 или 3 должно исправить это. - person Solomon Rutzky; 01.12.2017
comment
@Abe Кроме того, если объект SQLCLR возвращает значения для записи, почему он вообще подключается к базе данных? Чтобы получить значение для распаковки? Или это прошло, и что-то еще делается? - person Solomon Rutzky; 01.12.2017
comment
Спасибо за ваши комментарии. CLR написана на C# и используется для десериализации данных, т. е. для распаковки. SQL SP получает необработанные выходные данные. Вызывает CLR. CLR десериализует и возвращает десериализованные данные обратно в SQL. Затем SQL выполняет действия с распакованными данными. - person Abe; 04.12.2017
comment
@Abe Хорошо, спасибо за эту дополнительную информацию. Тем не менее, вы не ответили на вопрос, почему код SQLCLR подключается к базе данных / создает SqlConnection в первую очередь? Если он просто распаковывает и возвращает одно или несколько значений, зачем открывать соединение? И вы уже пробовали предложения 2 и 3? - person Solomon Rutzky; 04.12.2017