Тази грешка възниква, когато използвате блок try/catch вътре в транзакция. Нека разгледаме един тривиален пример:
SET XACT_ABORT ON
IF object_id('tempdb..#t') IS NOT NULL
DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)
BEGIN TRAN
INSERT INTO #t (i) VALUES (1)
INSERT INTO #t (i) VALUES (2)
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error, XACT_ABORT kills the batch
INSERT INTO #t (i) VALUES (4)
COMMIT TRAN
SELECT * FROM #t
Когато четвъртото вмъкване причини грешка, пакетът се прекратява и транзакцията се връща обратно. Засега няма изненади.
Сега нека се опитаме да се справим с тази грешка с блок TRY/CATCH:
SET XACT_ABORT ON
IF object_id('tempdb..#t') IS NOT NULL
DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)
BEGIN TRAN
INSERT INTO #t (i) VALUES (1)
INSERT INTO #t (i) VALUES (2)
BEGIN TRY
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH
INSERT INTO #t (i) VALUES (4)
/* Error the Current Transaction cannot be committed and
cannot support operations that write to the log file. Roll back the transaction. */
COMMIT TRAN
SELECT * FROM #t
Уловихме грешката с дублиращия се ключ, но иначе не сме по-добре. Нашата партида все още се прекратява и нашата транзакция все още се връща обратно. Причината всъщност е много проста:
Блоковете TRY/CATCH не засягат транзакциите.
Поради наличието на XACT_ABORT ON, в момента, в който възникне грешката на дублиращия се ключ, транзакцията е обречена. Това е направено за. Ранено е смъртоносно. Прострелян е в сърцето... и грешката е виновна. TRY/CATCH дава на SQL Server... лошо име. (съжалявам, не можах да устоя)
С други думи, няма да НИКОГА ангажират и ще ВИНАГИ бъде върнат назад. Всичко, което блокът TRY/CATCH може да направи, е да прекъсне падането на трупа. Можем да използваме XACT_STATE() функция, за да видим дали нашата транзакция е ангажирана. Ако не е, единствената опция е да отмените транзакцията.
SET XACT_ABORT ON -- Try with it OFF as well.
IF object_id('tempdb..#t') IS NOT NULL
DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)
BEGIN TRAN
INSERT INTO #t (i) VALUES (1)
INSERT INTO #t (i) VALUES (2)
SAVE TRANSACTION Save1
BEGIN TRY
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
IF XACT_STATE() = -1 -- Transaction is doomed, Rollback everything.
ROLLBACK TRAN
IF XACT_STATE() = 1 --Transaction is commitable, we can rollback to a save point
ROLLBACK TRAN Save1
END CATCH
INSERT INTO #t (i) VALUES (4)
IF @@TRANCOUNT > 0
COMMIT TRAN
SELECT * FROM #t
Тригерите винаги се изпълняват в контекста на транзакция, така че ако можете да избегнете използването на TRY/CATCH в тях, нещата са много по-прости.
За решение на вашия проблем CLR Stored Proc може да се свърже обратно към SQL Server в отделна връзка, за да изпълни динамичния SQL. Получавате способността да изпълнявате кода в нова транзакция, а логиката за обработка на грешки е лесна за писане и лесна за разбиране в C#.