mismatching number of BEGIN and COMMIT statements - sql-server

I have seen a few posts here about this problem but none seem to apply to my code. Here is the code that I have. Can anyone give me some advice about what I may be doing wrong. As far as I can see I do a COMMIT or ROLLBACK each time. However I always get the error below. Please note I edited the code and removed things after the inserts to make it small so it fits on the page.
CREATE PROCEDURE post_user_test
#ErrorMessage nvarchar(1000) OUTPUT,
#RC INT OUTPUT
AS
BEGIN
BEGIN TRY
DECLARE #SEQ INT;
BEGIN TRANSACTION
SELECT #SEQ = Isnull(#SEQ,0)
INSERT INTO dbo.UserTest .....
IF (##ROWCOUNT != 1) THROW 50001,'xxx',1
SET #UserTestId = SCOPE_IDENTITY()
INSERT INTO dbo.UserTestQuestion .....
IF (##ROWCOUNT != 1) THROW 50002,'xxx',1
SET #RC = 0
COMMIT TRANSACTION
RETURN
END TRY
BEGIN CATCH
SET #RC = 1
SET #ErrorMessage = ERROR_MESSAGE()
ROLLBACK TRANSACTION
RETURN
END CATCH
END
Here's the error message I am getting:
Unexpected error:{"message":"Transaction count after EXECUTE indicates
a mismatching number of BEGIN and COMMIT statements.

Here is an updated version of your code.. added ; as well as checked for rollback count in the catch block.. and removed RETURN statements
CREATE PROCEDURE post_user_test
#ErrorMessage nvarchar(1000) OUTPUT,
#RC INT OUTPUT
AS
BEGIN
BEGIN TRY
DECLARE #SEQ INT;
BEGIN TRANSACTION;
SELECT #SEQ = Isnull(#SEQ,0);
INSERT INTO dbo.UserTest .....;
IF (##ROWCOUNT != 1) THROW 50001,'xxx',1
SET #UserTestId = SCOPE_IDENTITY();
INSERT INTO dbo.UserTestQuestion .....;
IF (##ROWCOUNT != 1) THROW 50002,'xxx',1
SET #RC = 0;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
SET #RC = 1;
SET #ErrorMessage = ERROR_MESSAGE();
END CATCH
END;
Hope this helps..

Related

Looking to be sure that errors get caught properly in my stored procedure

I'm looking to see if I am able to capture my errors correctly in this stored procedure:
ALTER PROCEDURE [dbo].[sp_UpdateText]
(#aID AS INT,
#CompanyID AS INT,
#CompanyName AS VARCHAR(MAX))
AS
BEGIN
DECLARE #Result VARCHAR(MAX)
BEGIN TRY
SET #Result = (SELECT dbo.[udf_StripHTMLTags](#CompanyName)) -- UDF function that strips HTML tags off my text field
BEGIN TRANSACTION
UPDATE __TestTable1
SET CompanyName = #Result
WHERE aid = #aid AND CompanyID = #CompanyID
COMMIT TRANSACTION
END TRY
BEGIN CATCH
DECLARE #ErrorNumber INT = ERROR_NUMBER();
DECLARE #ErrorLine INT = ERROR_LINE();
PRINT 'ERROR NUMBER: ' + CAST(#ErrorNumber as Varchar(10));
PRINT 'ERROR LINE: ' + CAST (#ErrorLine as Varchar(10));
END CATCH
END
Go
I'm basically hoping that these BEGIN TRY BEGIN CATCH error capture methods will successfully capture errors, if arise? Any thought?
You should check out Erland's Guide to Error Handling
A suggestion from this inclusive guide would be to change your CATCH at a minimum to
BEGIN CATCH
IF ##trancount > 0 ROLLBACK TRANSACTION --roll back the tran
DECLARE #msg nvarchar(2048) = error_message() --error message is usually more helpful
DECLARE #ErrorNumber INT = ERROR_NUMBER();
DECLARE #ErrorLine INT = ERROR_LINE();
RAISERROR(#msg,16,1) --RAISE the error
RETURN 55555 --return a non-zero to application as non-success
END CATCH
There's a lot more in there which is why it's worth the read.
I almost forgot, SET XACT_ABORT, NOCOUNT ON at the top of your proc.
When you activate XACT_ABORT ON, almost all errors have the same
effect: any open transaction is rolled back and execution is aborted.
There are a few exceptions of which the most prominent is the
RAISERROR statement.
Note that “printing” the error would not store or log it anywhere, like the SQL Server Error log so you wouldn’t “catch” it at all.

How to select two record set only if second one was successful?

I am going to report result of a stored procedure (whether it was successful or has error) using a simple select statement before sending prepared record sets. so I just simply insert this select statement before sending real records sets. But even when I wrap these two select statement in a transaction to make them atomic yet if second select statement raises an error the first select executed and gives 'ok' and 'error' at the same time. here is the code:
CREATE PROCEDURE my_procedure
#id INT = NULL
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
SELECT 1 AS [status], 'OK' AS [message];
SELECT 1/0;
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK;
SELECT 0 AS [status], ERROR_MESSAGE() AS [message];
END CATCH;
END;
How could first select statement be done only if the the second statement is successful?
Maybe declare a couple of variables outsite of the TRY/CATCH. Then change their values in the CATCH if an error is thrown. After the TRY/CATCH, show the values of the variables.
ALTER PROCEDURE my_procedure
#id INT = NULL
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #status BIT = 1; --set status variable here
DECLARE #message VARCHAR(MAX) = 'OK'; --set message variable here
BEGIN TRY
BEGIN TRANSACTION;
SELECT 1/0;
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK;
SET #status = 0; --change value of #status in the CATCH block
SET #message = ERROR_MESSAGE();--change value of #message in the CATCH block
END CATCH;
--show the value of each variable
SELECT #status AS 'Status',#message AS 'Message'
END;
Have you tried using RAISERROR() or THROW()? Specifically, using RAISERROR() with a severity of 11-19 will force the execution to jump to the CATCH block. See details at https://learn.microsoft.com/en-us/sql/t-sql/language-elements/raiserror-transact-sql.
Specifically take a look at Example 1:
BEGIN TRY
-- RAISERROR with severity 11-19 will cause execution to
-- jump to the CATCH block.
RAISERROR ('Error raised in TRY block.', -- Message text.
16, -- Severity.
1 -- State.
);
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- Use RAISERROR inside the CATCH block to return error
-- information about the original error that caused
-- execution to jump to the CATCH block.
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH;

Uncommitable transaction prevents error logging in nested transaction

According to this, you can have a state in the catch block where you can't do any write operations unless you rollback first.
This is an issue when you're attempting to handle nested transactions and do error logging. In the following example, the exception in the nested procedure gets lost and nothing is logged.
IF OBJECT_ID(N'dbo.ErrorLog', N'U') IS NOT NULL
DROP TABLE dbo.ErrorLog;
GO
CREATE TABLE dbo.ErrorLog (Error NVARCHAR(4000));
GO
IF OBJECT_ID(N'tempdb..#Caller') IS NOT NULL
BEGIN
DROP PROC #Caller;
END;
GO
CREATE PROCEDURE #Caller
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #transCount TINYINT = ##TRANCOUNT,
#returnCode INT,
#errorMessage NVARCHAR(4000),
#errorNumber INT;
BEGIN TRY
IF (#transCount = 0)
BEGIN
BEGIN TRAN;
END;
EXEC #returnCode = #Called;
IF (#returnCode <> 0)
BEGIN
RAISERROR(N'Error in Called. Caller returned an error', 16, -1);
END;
IF (#transCount = 0)
BEGIN
COMMIT TRAN;
END;
END TRY
BEGIN CATCH
IF ((#transCount = 0) AND (XACT_STATE() <> 0))
BEGIN
ROLLBACK TRAN;
END;
SELECT #errorMessage = ERROR_MESSAGE(),
#errorNumber = ERROR_NUMBER();
INSERT dbo.ErrorLog(Error) VALUES(#errorMessage); --only this logging happens
RAISERROR(N'Error in Caller.', 16, -1);
RETURN #errorNumber;
END CATCH;
RETURN;
END;
GO
IF OBJECT_ID(N'tempdb..#Called') IS NOT NULL
BEGIN
DROP PROC #Called;
END;
GO
CREATE PROC #Called
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #transCount TINYINT = ##TRANCOUNT,
#errorMessage NVARCHAR(4000),
#errorNumber INT;
BEGIN TRY
IF (#transCount = 0) --doesn't start tran, already in one
BEGIN
BEGIN TRAN;
END;
SELECT 1/0; --generate an error; this exception gets lost
IF (#transCount = 0)
BEGIN
COMMIT TRAN;
END;
END TRY
BEGIN CATCH
IF ((#transCount = 0) AND (XACT_STATE() <> 0)) --cannot rollback here because this didn't start the transaction
BEGIN
ROLLBACK TRAN;
END;
SELECT #errorMessage = ERROR_MESSAGE(),
#errorNumber = ERROR_NUMBER();
INSERT dbo.ErrorLog(Error) VALUES(#errorMessage); --doesn't happen because of uncommitable transaction; raises exception, caught in CATCH block of Caller
RAISERROR(N'Error in Called.', 16, -1); --this doesn't happen
RETURN #errorNumber; --nothing returned
END CATCH;
RETURN;
END
GO
EXEC dbo.#Caller;
GO
SELECT * FROM dbo.ErrorLog;
GO
The single error logged is just the uncommitable transaction exception. Is there any way to handle nested transactions, in a TRY..CATCH, and still log errors that actually occur?
This approach helped me accomplish what I needed. Basically, in the case of a doomed tran (XACT_STATE() = -1), the entire transaction is rolled back to allow error logging at both levels while raising just one exception at the end of execution.
IF OBJECT_ID(N'dbo.ErrorLog', N'U') IS NOT NULL
DROP TABLE dbo.ErrorLog;
GO
CREATE TABLE dbo.ErrorLog (Error NVARCHAR(4000));
GO
IF OBJECT_ID(N'tempdb..#Caller') IS NOT NULL
BEGIN
DROP PROC #Caller;
END;
GO
CREATE PROCEDURE #Caller
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #transCount TINYINT = ##TRANCOUNT,
#returnCode INT,
#errorMessage NVARCHAR(4000),
#errorNumber INT;
BEGIN TRY
IF (#transCount = 0)
BEGIN
BEGIN TRAN;
END;
EXEC #returnCode = #Called;
IF (#returnCode <> 0)
BEGIN
RAISERROR(N'Error in Called. Caller returned an error', 16, -1);
END;
IF (#transCount = 0)
BEGIN
COMMIT TRAN;
END;
END TRY
BEGIN CATCH
IF (((#transCount = 0) AND (XACT_STATE() <> 0)) OR (XACT_STATE() = -1))
BEGIN
ROLLBACK TRAN;
END;
SELECT #errorMessage = ERROR_MESSAGE(),
#errorNumber = ERROR_NUMBER();
INSERT dbo.ErrorLog(Error) VALUES(#errorMessage); --only this logging happens
RAISERROR(N'Error in Caller.', 16, -1);
RETURN #errorNumber;
END CATCH;
RETURN;
END;
GO
IF OBJECT_ID(N'tempdb..#Called') IS NOT NULL
BEGIN
DROP PROC #Called;
END;
GO
CREATE PROC #Called
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #transCount TINYINT = ##TRANCOUNT,
#errorMessage NVARCHAR(4000),
#errorNumber INT;
BEGIN TRY
IF (#transCount = 0) --doesn't start tran, already in one
BEGIN
BEGIN TRAN;
END;
SELECT 1/0; --generate an error; this exception gets lost
IF (#transCount = 0)
BEGIN
COMMIT TRAN;
END;
END TRY
BEGIN CATCH
IF (((#transCount = 0) AND (XACT_STATE() <> 0)) OR (XACT_STATE() = -1)) --rollback even though didn't start this tran because its doomed
BEGIN
ROLLBACK TRAN;
END;
SELECT #errorMessage = ERROR_MESSAGE(),
#errorNumber = ERROR_NUMBER();
INSERT dbo.ErrorLog(Error) VALUES(#errorMessage); --doesn't happen because of uncommitable transaction; raises exception, caught in CATCH block of Caller
RAISERROR(N'Error in Called.', 16, -1); --this doesn't happen
RETURN #errorNumber; --nothing returned
END CATCH;
RETURN;
END
GO
DECLARE #returnCode INT;
EXEC #returnCode = dbo.#Caller; --one exception raised
SELECT #returnCode AS 'returnCode'; --error code 50000 returned
SELECT * FROM dbo.ErrorLog; --both errors logged incl original exception

ERROR_PROCEDURE() returning NULL for code executed by SP_EXECUTESQL

I have a stored procedure with a TRY CATCH statement and within that TRY CATCH I am calling another stored procedure which is throwing an error. An exception is thrown and caught however if the error is within the called stored procedure this is not shown in the ERROR_PROCEDURE() it is set to NULL. It seems the reason is due to Dynamic SQL being executed in the called stored procedure.
ALTER PROC dbo.MyError AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN
--do stuff here
--SQL CODE
SELECT 'HELLO' AS hello
--then call sproc
EXEC dbo.MyInnerError
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION
END
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
GO
ALTER PROC dbo.MyInnerError AS
BEGIN
DECLARE #SQl nvarchar(50) = 'SELECT 1/0 as DYNAMIC_FAIL';
EXEC SP_EXECUTESQL #SQl;
END
EXEC dbo.MyError
GO
I have tried nesting the stored procedure in its own TRY CATCH but this leads to TRANSACTION ROLLBACK issues.
Is ERROR_PROCEDURE() NULL because it is out of scope? And is there a way to set this?
It seems the reason is due to Dynamic SQL being executed in called Stored procedure. Is there a way to handle this?
Edit based on updated question and comments
ERROR_PROCEDURE() will not return a procedure name for SQL executed via SP_EXECUTESQL. Logically, if it did, it would return 'SP_EXECUTESQL' :). See this Connect entry "TRY/CATCH: ERROR_PROCEDURE() does not report name of procedure if error occured in dynamic SQL", in particular this sentence in the response from Microsoft;
Since there is no name associated with the ad hoc SQL, ERROR_PROCEDURE
will return NULL for errors raised from the execution level of the ad
hoc SQL.
I knocked up a very quick test and it works for me (SQL Server 2012);
CREATE PROC dbo.MyError AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN
--do stuff here
--SQL CODE
--then call sproc
EXEC dbo.MyInnerError
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION
END
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
GO
CREATE PROC dbo.MyInnerError AS
BEGIN
;THROW 51000, 'This is my only error.', 1;
END
GO
EXEC dbo.MyError
GO
Result is;
ErrorNumber ErrorSeverity ErrorState ErrorProcedure ErrorLine ErrorMessage
----------- ------------- ----------- ---------------- ----------- ------------------------
51000 16 1 MyInnerError 4 This is my only error.
The issue is that the sub-proc call to MyInnerError isn't failing; it is a call within MyInnerError that is failing but yet MyInnerError is completely successfully.
MyInnerError is completing successfully because you are not trapping the error and reporting a failure like you are doing in the outer proc via the TRY / CATCH structure.
That, and errors coming from Dynamic SQL will naturally never set ERROR_PROCEDURE().
All of your procs should have the TRY / CATCH structure and the CATCH block should use either RAISERROR or THROW (depending on what version of SQL Server you are using) so that you can bubble the error up to the calling scope.
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
... one or more SQL statements ...
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
---- If using SQL Server 2008, replace the above (from "IF" through "THROW")
---- with the following.
-- DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE();
-- RAISERROR(#ErrMessage, 16, 1);
-- RETURN;
END CATCH;
The IF (ERROR_PROCEDURE() IS NULL) block is used to catch situations like this where there is no proc generating the error. Calling ;THROW; by itself will bubble-up the current error info, which in this case is NULL for ERROR_PROCEDURE().
If you want to test this yourself, just run the following SQL which creates 3 stored procedures, all of which use the structure shown above. The inner-most procedure (ErrorTest1) calls sp_executesql using SELECT 1/0; as the query. The "divide by zero" error is trapped by the TRY / CATCH. The ERROR_PROCEDURE() function returns NULL as it was Dynamic SQL that generated the error. So, RAISERROR is called (technically calling ;THROW 50505, #ErrMessage, #ErrState; would work as well) to indicate to the calling process that the current proc generated an error.
Test Setup:
IF (OBJECT_ID(N'ErrorTest3') IS NOT NULL)
BEGIN
DROP PROCEDURE ErrorTest3;
END;
IF (OBJECT_ID(N'ErrorTest2') IS NOT NULL)
BEGIN
DROP PROCEDURE ErrorTest2;
END;
IF (OBJECT_ID(N'ErrorTest1') IS NOT NULL)
BEGIN
DROP PROCEDURE ErrorTest1;
END;
GO
CREATE PROCEDURE dbo.ErrorTest1
AS
SET NOCOUNT ON;
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
SELECT '1a';
EXEC sp_executesql N'SELECT 1/0 AS [ForceError];';
SELECT '1b';
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
END CATCH;
GO
CREATE PROCEDURE dbo.ErrorTest2
AS
SET NOCOUNT ON;
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
SELECT '2a';
EXEC dbo.ErrorTest1;
SELECT '2b';
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
END CATCH;
GO
CREATE PROCEDURE dbo.ErrorTest3
AS
SET NOCOUNT ON;
DECLARE #InNestedTransaction BIT = 0;
BEGIN TRY
IF (##TRANCOUNT > 0)
BEGIN
SET #InNestedTransaction = 1;
END;
ELSE
BEGIN
BEGIN TRAN;
END;
SELECT '3a';
EXEC dbo.ErrorTest2;
SELECT '3b';
COMMIT;
END TRY
BEGIN CATCH
IF (#InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
SELECT ERROR_PROCEDURE() AS [ErrorProcedure],
ERROR_STATE() AS [ErrorState],
ERROR_SEVERITY() AS [ErrorSeverity];
IF (ERROR_PROCEDURE() IS NULL)
BEGIN
DECLARE #ErrMessage NVARCHAR(4000) = ERROR_MESSAGE(),
#ErrState TINYINT = ERROR_STATE(),
#ErrSeverity TINYINT = ERROR_SEVERITY();
RAISERROR(#ErrMessage, #ErrSeverity, #ErrState);
RETURN;
END;
;THROW; -- introduced in SQL Server 2012
END CATCH;
GO
Run Test:
EXEC dbo.ErrorTest3;
Returns:
5 result sets:
3a
2a
1a
<empty>
ErrorProcedure ErrorState ErrorSeverity
ErrorTest1 1 16
And in the "Messages" tab:
Msg 50000, Level 16, State 1, Procedure ErrorTest1, Line 36
Divide by zero error encountered.

Transaction count after EXECUTE error

I have a stored procedure that looks something like:
CREATE PROCEDURE my_procedure
#val_1 INT,
#val_2 INT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO table_1(col_1, col_2)
VALUES (#val_1, #val_2);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
DECLARE
#ERROR_SEVERITY INT,
#ERROR_STATE INT,
#ERROR_NUMBER INT,
#ERROR_LINE INT,
#ERROR_MESSAGE NVARCHAR(4000);
SELECT
#ERROR_SEVERITY = ERROR_SEVERITY(),
#ERROR_STATE = ERROR_STATE(),
#ERROR_NUMBER = ERROR_NUMBER(),
#ERROR_LINE = ERROR_LINE(),
#ERROR_MESSAGE = ERROR_MESSAGE();
RAISERROR('Msg %d,
Line %d,
:%s',
#ERROR_SEVERITY,
#ERROR_STATE,
#ERROR_NUMBER,
#ERROR_LINE,
#ERROR_MESSAGE);
END CATCH
When this code is executed through the database, everything runs correctly. When execute through ADO.NET I get back the following error message:
"The INSERT statement conflicted with the FOREIGN KEY constraint "FK_table1_table2". The conflict occurred in database "my_database", table "dbo.table_1", column 'col_1'. Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0. "
Is this happening because the XACT_ABORT setting is forcing a transaction from ADO.NET to be rolled back? What's the best way to go about avoiding this error?
you can check XACT_STATE() in your code and then commit or rollback, check it out here: Use XACT_STATE() To Check For Doomed Transactions
basically something like this will blow up
BEGIN TRANSACTION TranA
BEGIN TRY
DECLARE #cond INT;
SET #cond = 'A';
END TRY
BEGIN CATCH
PRINT 'a'
END CATCH;
COMMIT TRAN TranA
and when you check xact_state you can control it
BEGIN TRANSACTION TranA
BEGIN TRY
DECLARE #cond INT;
SET #cond = 'A';
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE();
END CATCH;
IF XACT_STATE() =0
BEGIN
COMMIT TRAN TranA
END
ELSE
BEGIN
ROLLBACK TRAN TranA
END
Also take a look at these two must read links
Implementing Error Handling with Stored Procedures and Error Handling in SQL Server – a Background.
IF XACT_STATE() =0 BEGIN COMMIT TRAN TranA END
will generate erro. XACT_STATE() = 0 means there is no transaction to commit or rollback
XACT_STATE() = 1 means there is commitable transaction
XACT_STATE() = -1 means there is uncommitable transaction which will be rollbacked by Database engine at the end of current context.

Resources