K této chybě dochází při použití bloku try/catch uvnitř transakce. Podívejme se na triviální příklad:
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
Když čtvrté vložení způsobí chybu, dávka se ukončí a transakce se vrátí zpět. Zatím žádné překvapení.
Nyní se pokusme tuto chybu vyřešit pomocí bloku 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
Zachytili jsme chybu duplicitního klíče, ale jinak na tom nejsme lépe. Naše dávka se stále ukončuje a naše transakce se stále vrací zpět. Důvod je ve skutečnosti velmi jednoduchý:
Blokování TRY/CATCH neovlivňuje transakce.
Vzhledem k tomu, že je XACT_ABORT ON, v okamžiku, kdy dojde k chybě duplicitního klíče, je transakce odsouzena k zániku. Je hotovo pro. Bylo smrtelně zraněno. Bylo to střeleno do srdce...a chyba je na vině. TRY/CATCH dává SQL Server...špatné jméno. (promiň, nemohl jsem odolat)
Jinými slovy, NIKDY zavázat a bude VŽDY být vrácen zpět. Jediné, co blok TRY/CATCH dokáže, je zlomit pád mrtvoly. Můžeme použít XACT_STATE() funkce, abyste zjistili, zda je naše transakce potvrditelná. Pokud tomu tak není, jedinou možností je odvolat transakci.
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
Spouštěče se vždy provádějí v kontextu transakce, takže pokud se v nich můžete vyhnout použití TRY/CATCH, věci jsou mnohem jednodušší.
Pro řešení vašeho problému by se CLR Stored Proc mohl připojit zpět k serveru SQL Server v samostatném připojení a spustit dynamické SQL. Získáte možnost spustit kód v nové transakci a logika zpracování chyb se snadno píše a je snadno pochopitelná v C#.