I thought about this topic for a while and came up with a very simple solution that I didn't see before so I wanted to share this:
As it is impossible to rethrow the same error, one has to throw an error that is very easy to map to the original error for example by adding a fixed number like 100000 to every system error.
After the newly mapped messages are added to the database it is possible to throw any system error with a fixed offset of 100000.
Here is the code for creating the mapped messages (this has to be done only one time for the whole SQL Server Instance. Avoid clashes with other user defined messages by adding an appropriate offset like 100000 in this case):
DECLARE messageCursor CURSOR
READ_ONLY
FOR select
message_id + 100000 as message_id, language_id, severity, is_event_logged, [text]
from
sys.messages
where
language_id = 1033
and
message_id < 50000
and
severity > 0
DECLARE
@id int,
@severity int,
@lang int,
@msgText nvarchar(1000),
@withLog bit,
@withLogString nvarchar(100)
OPEN messageCursor
FETCH NEXT FROM messageCursor INTO @id, @lang, @severity, @withLog, @msgText
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
set @withLogString = case @withLog when 0 then 'false' else 'true' end
exec sp_addmessage @id, @severity, @msgText, 'us_english', @withLogString, 'replace'
END
FETCH NEXT FROM messageCursor INTO @id, @lang, @severity, @withLog, @msgText
END
CLOSE messageCursor
DEALLOCATE messageCursor
And this is the code to raise the newly created error codes that have a fix offset from the original code:
SELECT
@ErrorNumber = ERROR_NUMBER(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE()
set @MappedNumber = @ErrorNumber + 100000;
RAISERROR
(
@MappedNumber,
@ErrorSeverity,
1
);
There is one small caveat: You can't supply a message on your own in this case. But this can be circumvented by adding an additional %s in the sp_addmessage call or by changing all mapped messages to your own pattern and supplying the right parameters in the raiseerror call
In the client you can now do all the ordinary exception handling like the original messages would have been thrown, you only have to remember to add the fix offset. You can even handle original and rethrown exceptions with the same code like this:
switch(errorNumber)
{
case 8134:
case 108134:
{
}
}
So you don't even have to know if it is a rethrown or the original error, it's always right, even if you forgot to handle your errors and the original error slipped through.
There are some enhancement mentioned elsewhere concerning raising messages you can't raise or states you can't use. Those are left out here to show only the core of the idea.