Denne fejl opstår, når du bruger en try/catch-blok inde i en transaktion. Lad os overveje et trivielt eksempel:
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 fjerde indsættelse forårsager en fejl, afsluttes batchen, og transaktionen ruller tilbage. Ingen overraskelser indtil videre.
Lad os nu forsøge at håndtere den fejl med en TRY/CATCH-blok:
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 fangede dubletnøglefejlen, men ellers er vi ikke bedre stillet. Vores batch bliver stadig afsluttet, og vores transaktion bliver stadig rullet tilbage. Årsagen er faktisk meget enkel:
TRY/CATCH-blokeringer påvirker ikke transaktioner.
På grund af at have XACT_ABORT TIL, i det øjeblik dubletnøglefejlen opstår, er transaktionen dømt. Det er gjort for. Den er blevet dødeligt såret. Det er blevet skudt gennem hjertet... og fejlen er skyld. TRY/CATCH giver SQL Server...et dårligt navn. (undskyld, kunne ikke lade være)
Med andre ord vil det ALDRIG begå og vil ALTID blive rullet tilbage. Alt en TRY/CATCH-blok kan gøre er at bryde ligets fald. Vi kan bruge XACT_STATE() funktion for at se, om vores transaktion er forpligtende. Hvis det ikke er det, er den eneste mulighed at rulle transaktionen tilbage.
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
Triggere udføres altid i forbindelse med en transaktion, så hvis du kan undgå at bruge TRY/CATCH inde i dem, er tingene meget enklere.
For en løsning på dit problem kunne en CLR Stored Proc oprette forbindelse tilbage til SQL Server i en separat forbindelse for at udføre den dynamiske SQL. Du får muligheden for at udføre koden i en ny transaktion, og fejlhåndteringslogikken er både nem at skrive og let at forstå i C#.