by
Dean Cooper >> Fri, 4 Dec 1998 2:55:30 GMT
I've noticed that I sometimes end up with broken inverses (eg: a customer has a reference to its company, but does not appear in the allCustomers dictionary on the company) after handling an exception using my exception handler methods.
Theo,
You need to be careful handling exceptions in transaction state, particularly when your handler resumes the exception (by returning Ex_Resume_Next).
Using your customer/company example, assume we have a class Customer with a string property "name", and a class Company. There is a member key dictionary CustDict (membership Customer, keyed by "name") and a one-to-many relationship between Customer and Company implemented by Customer::myCoy of type Company and Company::allCustomers of type CustDict. These classes and the following three JadeScript methods demonstrate how an exception handler can break inverses:
// ---- createStuff ------------------------------
createStuff();
vars
coy : Company;
cust : Customer;
begin
// Make sure we have a company
coy := Company.firstInstance;
if coy = null then
beginTransaction;
create coy persistent;
commitTransaction;
endif;
// Arm our exception handler
on Exception do exHandler(exception);
// Create a duplicate customer
beginTransaction;
createCust("Theo", coy); // this will work
createCust("Theo", coy); // this will cause a 1310
commitTransaction;
// Check the customer instances
foreach cust in Customer.instances do
write cust.String & ": name=" & cust.name &
", myCoy=" & cust.myCoy.String;
endforeach;
// Check the customers on the company
foreach cust in coy.allCustomers do
write cust.String & ": name=" & cust.name &
", myCoy=" & cust.myCoy.String;
endforeach;
end;
// ---- createCust ------------------------------
createCust(custName : String; coy : Company);
vars
cust : Customer;
begin
create cust persistent;
cust.name := custName;
cust.myCoy := coy;
end;
// ---- exHandler ------------------------------
exHandler(e : Exception) : Integer;
begin
// An exception handler that just resumes
write "Resuming exception " & e.errorCode.String;
return Ex_Resume_Next;
end;
Executing createStuff will produce something like:
Resuming exception 1310
Customer/2071.3: name=Theo, myCoy=Company/2073.1
Customer/2071.4: name=Theo, myCoy=Company/2073.1
Customer/2071.3: name=Theo, myCoy=Company/2073.1
This shows that there are two customer instances in the database (2071.3 and 2071.4) both referencing the single company, but only one of the customers (2071.3) appears in the allCustomers dictionary on the company.
Why? Because the exception handler returns Ex_Resume_Next. This tells JADE to continue execution at the next statement in the method that armed the exception handler (the commitTransaction in createStuff). The exception is raised by JADE's automatic inverse maintenance when it attempts to add the second customer to allCustomers and finds that there is a duplicate key. By this stage though, the second customer has been created and its myCoy reference assigned. By resuming the exception and committing the transaction only one half of the relationship (the customer side) is committed.
When resuming exceptions (particularly SystemExceptions) carefully consider what persistent transactions may be in progress and what the effects of a resume may be. In general it is dangerous to resume exceptions in transaction state unless the exception handler is well defined and specific to the transaction; a generic handler should normally abort.
Note that aborting an action (by returning Ex_Abort_Action) and aborting a transaction (via abortTransaction) are different concepts; one does not cause the other. If you want your exception handler to abort both the transaction and the action you must code abortTransaction before returning Ex_Abort_Action. If you return Ex_Pass_Back to pass control back to JADE's default exception handler dialog, it will abort the transaction if the user clicks the abort button.
Dean.