3.5 Setting a Concurrency Model Globally
You can disable the table-level concurrency
settings at run time. Disabling these settings has a global impact on
the business logic, however. You can override the table-level setting
and enforce either optimistic or pessimistic concurrency for all tables
by using the Concurrency Model Configuration form from the
Administration menu. The property on the tables doesn’t change, but when
the Dynamics AX application runtime interprets the forupdate keyword, it uses the global setting rather than the table-level setting. The global setting honors the interpretation of the optistimiclock or pessimisticlock keyword, so optimistic and pessimistic concurrency are still enforced in scenarios in which these keywords are used.
Warning
You
should disable the table-level settings with great care and only after
considerable testing in a nonproduction environment—and only if you
completely understand and accept all the consequences of the change. |
3.6 Optimistic Concurrency and Exception Handling
“The X++ Programming Language,” it deserves special attention in a discussion of optimistic concurrency because an UpdateConflict exception is thrown when the application runtime discovers an update conflict. The UpdateConflict
exception is one of only two exceptions that can be caught both inside
and outside a transaction scope. All other exceptions in X++ can be
caught only outside a transaction scope. When the update conflict
exception is caught inside a transaction scope, the database isn’t
rolled back, as it is when caught outside a transaction scope.
Update conflict exceptions can be caught inside
a transaction scope so that you can catch the exception, execute
compensating logic, and then retry the update. The compensating logic
must insert, update, or delete records in the database to get to a state
in which you can retry the application logic.
There are two types of update conflicts exceptions, structured and unstructured. With structured exception handling, the catch block signature that is specific to this exception type contains an instance of the table variable. This catch
block is executed only when the update conflict happens on the table
instance specified in that signature. A structured exception handling
framework can be particularly useful when a block of code issues
multiple updates and the application intends to catch and recover from
an updateconflict exception in a table buffer. The following example demonstrates the use of a structured updateconflict exception.
static void Occ2StructuredMultipleUpdateConflictMgmt(Args _args)
{
CustTable cust1;
CustTable cust2;
;
ttsbegin;
try
{
select forupdate cust1 where cust1.AccountNum == '1101' &&
cust1.CustGroup == '10';
select forupdate cust2 where cust2.AccountNum == '1102' &&
cust2.CustGroup == '10';
cust1.CreditRating = strfmt("%1",str2int(cust1.CreditRating)+1);
cust2.CreditRating = strfmt("%1",str2int(cust2.CreditRating)+1);
cust2.update();
cust1.update();
}
catch(Exception::UpdateConflict, cust1)
{
ttsabort;
throw Exception::UpdateConflictNotRecovered;
}
catch(Exception::UpdateConflict, cust2)
{
cust2.reread();
cust2.CreditRating = strfmt("%1",str2int(cust2.CreditRating)+1);
cust2.update();
}
ttscommit;
}
|
You might find it very difficult, however, to
write compensation logic that reverts all changes within a given
scenario and makes it possible to retry the application logic from a
consistent state, especially because update methods can be customized to
manipulate records in other tables. These changed records are then not
compensated for by the compensation logic, which might be located in a
completely different element. Because of these difficulties, the
standard Dynamics AX application doesn’t attempt to compensate for
changes to database records and retry within a transaction scope. The
implemented X++ code to catch the update conflict exception and retry
outside transaction scopes uses the X++ code pattern shown in the
following example. The validation on the returned value from appl.ttsLevel
determines whether the exception is caught inside or outside the
transaction. If the exception is caught inside a transaction scope, the
exception is simply thrown again. If the exception is caught outside a
transaction scope, the transaction is retried unless the scenario has
already been retried a certain number of times, in which case the
application logic stops trying and throws an UpdateConflictNotRecovered exception. In Dynamics AX, the maximum number of retries, which is set in the OCCRetryCount macro element in the AOT, is 5.
#OCCRetryCount
catch (Exception::UpdateConflict)
{
if (appl.ttsLevel() == 0)
{
if (xSession::currentRetryCount() >= #RetryNum)
{
// Don't retry anymore.
throw Exception::UpdateConflictNotRecovered;
}
else
{
// Transaction is rolled back, so retry.
// Possible additional code here.
retry;
}
}
else
{
// Rethrow exception because execution is within transaction.
throw Exception::UpdateConflict;
}
}
|
3.7 Concurrency Models in Forms
The execution of the rich client and Web client
form application runtime always uses optimistic concurrency when
updating and deleting records in forms. This means that the form
application runtime doesn’t use the OccEnabled property on the tables.
In a Dynamics AX installation that uses SQL
Server 2005, records are always read into the form by using an
uncommitted isolation level, and when records are updated or deleted,
the RecVersion check is performed. This
check prevents an extra round-trip to the database to reselect the
record and requires an update lock. This was not the case in earlier
versions of Dynamics AX (Microsoft Axapta 3.x and earlier), in which
optimistic concurrency wasn’t not implemented.
3.8 Repeatable Read
If you don’t need to modify any data and merely
want to ensure that the same data can be read numerous times within a
transaction scope without changes, you can use the repeatable
read option supported in Dynamics AX. You ensure repeatable read by issuing the following select statement, which includes the repeatableread keyword.
select repeatableread custTable where custTable.CustGroup == '40';
|
When Dynamics AX running with SQL Server 2005 executes the preceding statement, it adds a REPEATABLEREAD hint to the SQL SELECT
statement, which is passed to the database. This hint ensures that a
shared lock is held until the end of the transaction on all records the
statement selects. Because the repeatableread
keyword prevents any other process from modifying the same records, it
guarantees that the same record can be reselected and that the field
values remain the same.
Caution
The repeatableread
option only prevents the records from being updated or deleted. It
doesn’t prevent the insertion of new records that match the criteria
applied when the shared locks were acquired. The same SELECT statement can therefore return more rows the second time it is executed. |