SQL Error Flow
A SAL When SqlError handler can return to the SQL operation that invoked it. A C# return inside a catch exits the enclosing method. A translation can therefore compile, report the error correctly, and still skip required business logic.
This walkthrough follows a failure for which the application must record the error, clean up an owned resource, and perform a failure-specific continuation. It does not prescribe a universal transaction policy.
- SQL callThe operation failsActive
- Local handlerRecord the error once
- Failure branchCleanup owned resources
- ContinuationRequired business logic
- Function exitEarly return hazard
The SQL overload was given the generated WhenSqlError delegate.
Operation: failedA conceptual sequence, not a live runtime trace. Playback advances every four seconds. The full explanation follows below.
Compare the Three Paths
| Strategy | Failure trace | Result |
|---|---|---|
| Generated delegate returning false | SQL call → local handler → false SQL result → caller's failure branch | The enclosing method decides whether to continue or return. |
| Catch containing an early return | SQL call → exception → catch → return from method | Statements left in the try block and later continuation can be skipped. |
| Explicit outcome and failure branch | SQL call → result or catch → test outcome → cleanup and failure continuation | Both failure routes join at an intentional point. |
The delegate's behavior is discussed in When SQLError. In this scenario it returns false. Do not generalize this trace to every possible handler return, retry, or exception raised by the handler itself.
Make the Continuation Explicit
The following is an application-method excerpt. RecordSqlFailure, CleanupOwnedResources, and AfterFailedSave stand for your existing application logic, not PPJ API methods.
using (new WhenSqlError())
{
bool succeeded = false;
try
{
succeeded = Sql.PrepareAndExecute(hSql, sSql);
}
catch (SalSqlError)
{
RecordSqlFailure(hSql);
}
if (!succeeded)
{
CleanupOwnedResources();
AfterFailedSave();
}
}
Keep the local WhenSqlError context: it tells PPJ that this scope handles SQL errors instead of dispatching them to the global application SQL-error handler. A try/catch alone does not communicate that intent to PPJ. Preserve any enclosing SAL/SQL contexts needed by the original method as well.
The narrow try block matters. It covers the SQL operation being translated, rather than treating an unrelated exception later in the method as if the SQL call failed. The failure continuation stays inside if (!succeeded) so it cannot run accidentally after success.
This pattern does not catch every provider exception from arbitrary raw ADO.NET code, nor does it suppress errors thrown by cleanup. Use the exception contract of the API actually called.
Decide Who Owns the Transaction
Do not blindly disconnect or roll back every handle in a local error handler. A handle can share a connection or participate in a larger operation. Record who opened the connection, who began the transaction, and who must commit or roll back it. A successful SQL statement inside an uncommitted transaction is not yet a durable business success.
If the application retries a write, establish whether the first attempt committed before retrying. A client timeout or disconnect alone does not prove the server performed no change. Preserve the application's duplicate-detection or idempotency rules.
Trace Both Success and Failure
For each translated handler, record the following against the original application:
- SQL operation and parameters, excluding secrets and sensitive values from ordinary logs.
- Whether the local or global handler runs, and how many times it runs.
- The result seen by the calling code and the next application statement executed.
- Cleanup of owned resources and the final transaction outcome.
- User-visible result and any allowed subsequent action.
Exercise a successful statement, a handled failure, a false result where the API can produce one, and a failure inside the handler/cleanup path. Use the actual provider's error codes rather than assuming Team Developer's historical numeric offsets remain valid. See ADO.NET Drivers.