Tuesday, 13 November 2007

New approach to Exceptions and Transactions handling

As much as I like the transactional support in the core of the X++ language, as much I dislike the connection between exception handling and transactions.

In my opinion transaction should be part of the syntax and have syntax based scope.

Here is my suggestion to Microsoft:

1) ban the standalone ttsbegin and ttscommit

2) replace it with special try-catch syntax , as drafted below:

try(new Connection())

{ //implicit TTSbegin

[do your stuff]

} //implicit TTScommit

catch(Exception::Error)

{ //implicit TTSabort.

[oops]

}

Both Oracle and MSSQL2005 support the nested transactions, and that would be ideal opportunity to use it.

conn = new Connection();

try(conn)

{ //implicit TTSbegin = BEGIN TRANS LEVEL1

try(conn)

{ //implicit TTSbegin = BEGIN TRANS LEVEL2

SalesFormLetterInvoice::post(SalesTable);

} //implicit TTScommit = COMMIT TRANS LEVEL2

catch(Exception::Error)

{ //implicit TTSabort = ROLLBACK TRANS LEVEL2

if(..cannot compensate the error...)

throw Error(....)

else

[compensate the error.]

}

} //implicit TTScommit = COMMIT TRANS LEVEL1

catch(Exception::Error)

{ //implicit TTSabort = ROLLBACK TRANS LEVEL1

[...]

}

That way each try-catch block is eligible to work, not only this started outside of transaction, and when needed each of them can operate own (sub) transaction.

It is also much less demanding on the developer, preventing improper or unbracketed use of ttsbegin and ttscommit. Normal (non db) try-catch bloack work as usually, allowing to catch X++ and CLR exceptions without unwanted rollbacks.

3 comments:

Max Belugin said...

I think it would be better and more general to borrow the 'using'keyword from C#

using(new Transaction())
{
select ...
}

Michał Kupczyk said...

That's even better, but I affraid 'using' clause does not distinguish the reason why object goes out of scope, which is important here. If execution arrives to end of scope implicit ttscommit should happen.

It need to act like:

using(new Transaction())
{ //implicit TTSbegin = BEGIN TRANS LEVEL1
select
[...]
if(....)
throw Error() //implicit TTSAbort = ROLLBACK TRANS LEVEL1

} //implicit TTSCOMMIT

Max Belugin said...

maybe, it should extend the 'using' semantic.

using (a)
{
xxx
}

==>

try
{
xxx
a.Dispose();
}
catch
{
a.ExceptionDispose();
}