In order to overcome the described limitations and difficulties with error handling using SQL Server's TRY...CATCH,
my advice is simple: when we need to implement feature-rich error
handling to respond intelligently to an anticipated error, we should do
it in a language that offers more robust error handling, such as C#.
By doing so, we avoid complications caused by doomed transactions (for example, trivial conversion errors in a C# TRY
block will never doom a transaction), or by error numbers being changed
when they are re-thrown, and so on. Furthermore, once error handling is
implemented in a C# class it can be reused by all modules that need it,
so we promote code reuse to its fullest extent.
Nowadays many of us
developers use more than one language in our daily activities, and the
reason is very simple and very pragmatic: in many cases it is much
easier to learn a new language to accomplish a specific task, to which
the language is well-suited, than it is to try to "bend" a single
language to all purposes.
By way of an example, Listing 1 re-implements in C# our "retry after deadlock" logic. We need only implement this logic once, and we can use this class to execute any command against SQL Server.
Let's try this class out. First of all, we need to remove the retry logic from our Change-CodeDescription stored procedure, but keep it just as prone to deadlocks as before. Listing 2 shows how to accomplish that.
Obviously we'd first need to
test this procedure and verify that it can successfully complete; a
step that I will leave as a simple exercise.
Rather than invoke our ChangeCodeDescription stored procedure from a second SSMS session, as before, we need to execute the C# code shown in Listing 3, which invokes the same stored procedure through our RetryAfterDeadlock method.
This method will not
complete, as the table is locked by our SSMS transaction. Return to SSMS
and highlight and execute the commented code, both the UPDATECOMMIT.
The transaction invoked from C# will be chosen as a deadlock victim and
it will retry, and there is enough debugging output in our C# code to
demonstrate what is happening. command and the
Finally, let us verify that, after the retry, the modification completed, as shown in Listing 4.
In short, C# allows us to
implement our "retry after deadlock" logic just once and reuse it as
many times as we need. As defensive programmers, we really want to reuse
our code, not to cut and paste the same code all over our systems, and
so we have a strong motivation to use a good modern tool such as C# for
our error handling.
My message here is quite
moderate. I am not suggesting that we abandon T-SQL error handling; far
from it. In the simplest cases, when all we need is to roll back and
raise an error, we should use XACT_ABORT and transactions. Notice that in Listing 8-23 we use XACT_ABORT and a transaction to roll back after a deadlock, but we implement all of the more complex error-handling logic in C#.
Of course, there are situations
when we do need to implement error handling in T-SQL. Whenever we are
considering such an option, we need to realize that error handling in
T-SQL is very complex and not really intuitive to a developer with
experience in other languages. Also, it has a lot of gotchas, and it
lacks some features which client-side programmers consider as their
birthright, such as the ability to re-throw an error exactly as it was
caught.