Det här felet uppstår när du använder ett försök/fångstblock i en transaktion. Låt oss överväga ett trivialt exempel:
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
När den fjärde infogningen orsakar ett fel, avslutas batchen och transaktionen rullar tillbaka. Inga överraskningar än så länge.
Låt oss nu försöka hantera det felet med ett TRY/CATCH-block:
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
Vi fångade dubblettnyckelfelet, men annars har vi det inte bättre. Vår batch avslutas fortfarande och vår transaktion återställs fortfarande. Anledningen är faktiskt väldigt enkel:
TRY/CATCH-blockeringar påverkar inte transaktioner.
På grund av att ha XACT_ABORT PÅ, i det ögonblick då dubblettnyckelfelet inträffar, är transaktionen dömd. Det är gjort för. Den har blivit dödligt skadad. Det har skjutits genom hjärtat... och felet är att skylla på. TRY/CATCH ger SQL Server...ett dåligt namn. (förlåt, kunde inte låta bli)
Med andra ord, det kommer ALDRIG begå och kommer ALLTID rullas tillbaka. Allt ett TRY/CATCH-block kan göra är att bryta likets fall. Vi kan använda XACT_STATE() funktion för att se om vår transaktion är genomförbar. Om det inte är det är det enda alternativet att återställa transaktionen.
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
Triggers körs alltid inom ramen för en transaktion, så om du kan undvika att använda TRY/CATCH inuti dem är saker mycket enklare.
För en lösning på ditt problem kan en CLR Stored Proc ansluta tillbaka till SQL Server i en separat anslutning för att exekvera den dynamiska SQL-en. Du får möjligheten att exekvera koden i en ny transaktion och felhanteringslogiken är både lätt att skriva och lätt att förstå i C#.