views:

152

answers:

2
i have this procedure for inserting rows in tables(sql server 2005)

CREATE PROCEDURE ans_insert
    (
    @q_desc varchar(2000),
    @sub_id int,
    @marks int,
    @ans1 varchar(1000),
    @ans varchar(1000),
    @userid varchar(15),
    @cr_date datetime

    )
    AS
    BEGIN
    BEGIN TRY
    BEGIN TRANSACTION
        DECLARE @q_id int

        insert into questions(q_desc,sub_id,marks,created_by,DT_created) values(@q_desc,@sub_id,@marks,@userid,@cr_date);
        SET @q_id = IDENT_CURRENT('questions')

        INSERT INTO answers(ans_desc,q_id,created_by,DT_created,istrue)
            VALUES( @ans1,@q_id,@userid,@cr_date,
            CASE WHEN @ans1 =@ans THEN 1 ELSE 0 END);
    COMMIT TRANSACTION

    END TRY
    BEGIN CATCH

        DECLARE @ErrorMessage NVARCHAR(4000);
        DECLARE @ErrorSeverity INT;
        DECLARE @ErrorState INT;
        DECLARE @ErrorLine INT;
        SELECT @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorLine=ERROR_LINE(),
        @ErrorState = ERROR_STATE();
        IF @@TRANCOUNT > 0 


    ROLLBACK TRANSACTION
        RAISERROR (@ErrorMessage,@ErrorSeverity,@ErrorState,@ErrorLine);
    END CATCH
    END

ans i call this from my ASP.NET form as

AnsCmd is my stored procedure command...after adding all params

               try
                {


                    conn.Open();

                    AnsCmd.ExecuteNonQuery();

                    lblMsg.Visible = true;
                    lblMsg.Text = "success";
                    conn.Close();
                }
                catch (SqlException sqlex)
                {
                    lblMsg.Visible = true;
                    lblMsg.Text = sqlex.ToString();
                }

                catch (Exception ex)
                {
                    lblMsg.Visible = true;
                    lblMsg.Text = ex.ToString();

                }

to check that raiserror is working,i changed the table name in insert from answers to answers1 which does not exist..

when executed i get error message as

System.Data.SqlClient.SqlException: Invalid object name 'answers1'. Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 0, current count = 1. at System.Data.SqlClient.SqlConnection.OnError(SqlException exception,......

so is this thing working properly or m i missing something???

+2  A: 

The batch (stored procedure) is aborting when it gets to the non-existent table (which is deferred name resolution), so the ROLLBACK is not executing.

From MSDN/BOL:

Compile and Statement-level Recompile Errors

There are two types of errors that will not be handled by TRY…CATCH if the error occurs in the same execution level as the TRY…CATCH construct:

  • Compile errors, such as syntax errors that prevent a batch from executing.
  • Errors that occur during statement-level recompilation, such as object name resolution errors that happen after compilation due to deferred name resolution.

When the batch, stored procedure, or trigger that contains the TRY…CATCH construct generates one of these errors, the TRY…CATCH construct does not handle these errors.

I suggest you add SET XACT_ABORT ON at the top. This will force a ROLLBACK on error and "tidy up".

One more thing...

SET @q_id = IDENT_CURRENT('questions')

should be

SET @q_id = SCOPE_IDENTITY()

Edit:

CREATE PROCEDURE ans_insert
    @q_desc varchar(2000),
    @sub_id int,
    @marks int,
    @ans1 varchar(1000),
    @ans varchar(1000),
    @userid varchar(15),
    @cr_date datetime
AS
BEGIN

SET NOCOUNT, XACT_ABORT ON; -- what I do

BEGIN TRY
....
gbn
+1 does it also matter that BEGIN TRANSACTION is after BEGIN TRY?
AdamRalph
@AdamRalph: no. the TXN will be started when it hits the statement where deferred name resolution fails. I pretty much do everything inside TRY/CATCH blocks
gbn
ok so if i use table name that actually exists and some error occurs during insert...then the transaction will rollback?? i.ewhatever i hv done is correct if table names are correct??--thanx
anay
@anay: Yes, it will work. I do similar, but I always use SET XACT_ABORT ON.
gbn
fair enough. sure, TRY/CATCH it definitely a good pattern, I was just thinking that perhaps the BEGIN TRANSACTION needs to be before BEGIN TRY. In an analgous C# example, you need to declare variables before a try{} block in order that they can be used within a catch{} block. I was thinking that this might also apply to the transaction, i.e. has to be started before BEGIN TRY to allow ROLLBACK TRANSACTION after BEGIN CATCH. If that's not the case then no worries.
AdamRalph
anay
@AdamRalph: your TXN handling could be anywhere, true, and you could have BEGIN inside/ROLLBACK outside. However, in this case the batch aborts and no more code is executed anyway. Which is why I suggest SET XACT_ABORT ON to force a ROLLBACK
gbn
@gbn:SET XACT_ABORT ON is written b4 Begin transaction or??
anay
@anay: I put it on the first line, immediately at start of stored proc
gbn
@gbn:that is after create procedure and declaration of params ??
anay
@anay: added an example
gbn
@gbn:hey thanx a lot...
anay
A: 

I don't think it'll affect the exception - but some thoughts:

  • would SCOPE_IDENTITY() be easier (and more reliable) than IDENT_CURRENT? IDENT_CURRENT can return an id from another session during parallel operations. Also avoid @@IDENTITY, which can be impacted by triggers doing INSERTs
  • why not let the calling (.NET) code worry about the transaction? It is far easier (and more versatile) to manage at a higher level, either on the connection (SqlTransaction) or wider (TransactionScope)

Example SqlTransaction approach:

using(SqlTransaction tran = conn.BeginTransaction()) {
    try {
        // operations (may need to set command.Transaction = tran)
        tran.Commit();
    } catch {
        tran.Rollback();
        throw;
    }
}

Example TransactionScope approach (** must SPAN the connection **)

using(TransactionScope tran = new TransactionScope()) {
    // operations: note no other changes
    tran.Complete();
}
Marc Gravell
This assume a .net client though
gbn
@mark--i m calling this procedure in a for loop and using dataadapters at also..i tried BeginTransaction but because i hv to to look for so many conn.open and close it is getting complex for me....thanx
anay
@gbn - I assumed only what was in the question; pretty much every client has transaction support.
Marc Gravell
`TransactionScope` removes most of this complexity, since you don't need to attach to individual commands.
Marc Gravell