Status: Reviewed (deep-dive v1)
Stack note: Write procs (usp_Alert_Ack, merges, Evaluate) and Agent jobs need predictable failures — catch, log, don’t leave half-open transactions. Blazor shows friendly errors from proc/RAISERROR messages; don’t swallow silently.
Pairs with: SQL-12 (proc skeleton), SQL-10 / SPIKE-07 (alerts), SQL-04 (Agent jobs).
Goal: TRY/CATCH, XACT_STATE, XACT_ABORT, logging patterns for warehouse + jobs.
| Situation | Pattern |
|---|---|
| Read-only board proc | Light touch — let errors bubble; optional outer TRY for logging |
| Write proc (Ack, merge) | SET XACT_ABORT ON + BEGIN TRY/TRAN + CATCH with XACT_STATE |
| Agent job step | TRY/CATCH + log table or RAISERROR so job history shows failure |
| “Continue on row errors” bulk | Prefer set-based + staging validation; don’t hide poison rows |
| Blazor needs a message | THROW; or RAISERROR with clear text after rollback |
CREATE OR ALTER PROC dbo.usp_Example_Write
@Id int,
@Actor nvarchar(128)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
-- validate
IF NOT EXISTS (SELECT 1 FROM dbo.Alert WHERE AlertId = @Id AND Status = N'Open')
THROW 50001, N'Alert not open or not found.', 1;
UPDATE dbo.Alert
SET Status = N'Acked', AckedAt = SYSUTCDATETIME(), AckedBy = @Actor
WHERE AlertId = @Id;
INSERT dbo.AlertEvent (AlertId, EventType, Actor, Message)
VALUES (@Id, N'Acked', @Actor, NULL);
COMMIT TRAN;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRAN;
-- optional: INSERT dbo.ProcErrorLog (…)
THROW; -- re-raise original error to Blazor/Agent
END CATCH
END;
XACT_STATE cheat sheet| Value | Meaning | Action in CATCH |
|---|---|---|
1 |
Transaction usable | ROLLBACK (or commit only if you truly know it’s safe — usually rollback) |
0 |
No transaction | Nothing to roll back |
-1 |
Uncommittable (doomed) | Must ROLLBACK; cannot commit |
With XACT_ABORT ON, many errors doom the tran → expect -1 and always roll back.
TRY/CATCH vs XACT_ABORTXACT_ABORT ON: Most runtime errors abort the batch and doom the tran — pairs well with TRY/CATCH for a single cleanup path XACT_ABORT OFF (default): Some errors are catchable without dooming — easier to leave partial work if you’re sloppy XACT_ABORT ON + TRY/CATCH + THROWNote: Not all errors enter CATCH (e.g. some compile-time / certain connection-level issues). Jobs should still check step success.
THROW |
RAISERROR |
|
|---|---|---|
| Re-raise in CATCH | THROW; (preserves error) |
Manual rebuild — easy to lose number/line |
| Custom message | THROW 50001, N'msg', 1; |
Flexible severity/state |
| Preference | Default in new code | Legacy / special severity needs |
Custom numbers: use ≥ 50000. Document codes your Blazor layer maps (e.g. 50001 = not found / conflict).
-- lean shape
CREATE TABLE dbo.ProcErrorLog (
LogId bigint IDENTITY PRIMARY KEY,
LoggedAt datetime2(0) NOT NULL CONSTRAINT DF_ProcErrorLog_At DEFAULT SYSUTCDATETIME(),
ProcName sysname NULL,
ErrorNumber int NULL,
ErrorSeverity int NULL,
ErrorState int NULL,
ErrorLine int NULL,
ErrorMessage nvarchar(4000) NULL,
Actor nvarchar(128) NULL
);
-- In CATCH before THROW:
-- INSERT dbo.ProcErrorLog (ProcName, ErrorNumber, …)
-- SELECT ERROR_PROCEDURE(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_LINE(), ERROR_MESSAGE(), @Actor;
THROW after rollback RETURN 0 after swallow usp_Alert_Evaluate should CATCH per-section or whole batch, log, and rethrow or set job fail — silent evaluate = blind boards CATCH blocks COMMIT in CATCH “to save what we can” without knowing XACT_STATE RETURN 0 so Agent shows success Usually no explicit tran. Optional:
BEGIN TRY
SELECT …;
END TRY
BEGIN CATCH
THROW;
END CATCH
Enough to standardize logging later; don’t over-engineer GetBoard.
| Symptom | Likely cause |
|---|---|
| “Transaction count after EXECUTE…” | Missing rollback / nested tran mismatch |
| Job green but data wrong | CATCH swallowed error |
| Blazor timeout, partial ack | Long tran; no XACT_ABORT; client cancel |
| Uncommittable transaction | Errored mid-tran without rollback |
| Duplicate event rows | Retry without idempotent write |
XACT_ABORT ON — confirm rollback + THROW ProcErrorLog then rethrow THROW fires XACT_ABORT + XACT_STATE + THROW adopted SQL Dude — SQL-15 T-SQL Error Handling v1