Browse DevX
Sign up for e-mail newsletters from DevX


Introducing the CursorAdapter Class : Page 3

One of the most exciting new features of Visual FoxPro 8 is the CursorAdapter class, which provides a common interface for working with data from many different sources. Chuck takes you with him on an adventure in exploring how to use CursorAdapter to change the way you relate to data in VFP 8, whether native tables, ODBC, OLE DB, or XML.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Handling Errors
Obviously, not everything will go as planned when trying to update data from the CursorAdapter. As you well know, TableUpdate can fail for a variety of reasons, such as an update conflict or a record lock. Do you have to do anything special with the CursorAdapter class to detect these problems? The answer is, "it depends."

Let's create a simple update problem by locking the record that the CursorAdapter is attempting to update. If the class designer is still open, close it. Then, instantiate the deTest class with the NewObject function, just as you did above, and call the OpenTables method. Browse the cursor so that you can see the data, but don't change anything yet.

You'll have to ensure that any CursorAdapter object variables stay within scope for as long as you intend to access the associated cursor.
Now open a second instance of VFP 8 so you can lock the record. Execute the following lines in the command window to lock the record that you'll attempt to update:

   OPEN DATABASE (HOME(2)+"Northwind\northwind.dbc")
   USE customers
   LOCATE FOR customerid = 'CACTU'
You should get a return value of .T. to show that the record is actually locked by this instance of VFP.

Return to the first instance of VFP and attempt the following code from the command window:

   REPLACE contactname WITH 'updated'
In this case, TableUpdate returns .F., showing that the record lock prevented the update from succeeding. If you issue a call to AERROR() and display the contents of the resultant array, you will see the error message "Record is not locked." This means that you can handle such errors in the same way as if you were working directly with the buffered table and not a cursor.

Unfortunately, not all expected errors will behave this way. Of particular note is the Update Conflict, where an update made by one user attempts to overwrite the changes made by another user. To see this in action, issue the following commands in the current instance of VFP (where the CursorAdapter is being used):

   REPLACE contactname WITH 'client 1'
Now switch over to the second instance and issue the following commands:

   OPEN DATABASE (HOME(2) + "Northwind\northwind.dbc")
   USE customers
   LOCATE FOR customerid = 'CACTU'
   REPLACE contactname WITH 'client 2'
Return to the first instance, and attempt to update the changes with TableUpdate:

In this case, TableUpdate incorrectly returns a .T., leading you to believe that the update was successful! However, it was not, and this can be proven by invoking the CursorRefresh() method of the CursorAdapter, as in the following code:

The CursorRefresh method tells the CursorAdapter to re-execute the SelectCmd and retrieve the latest data from the base table. Examination of the ContactName field shows that the value was never updated from the CursorAdapter!

The easiest way to solve this problem is to take advantage of the AfterUpdate method on the CursorAdapter. This method is invoked after the TableUpdate attempts to save the changes to each record in the result set. Note that this method is invoked only for a change to a current record. If the record is new or the record is deleted, then the AfterInsert or AfterDelete methods would fire.

The AfterUpdate method captures several parameters, including the original field state, whether changes were forced, and the text of the commands that were used for the update. The last parameter, lResult, is the most critical for our current topic, as it tells us whether the update was deemed a success by the updating process.

The other feature to use to solve the update conflict problem is the system variable _TALLY, which tells how many records were affected by the last operation. Therefore, if lResult is true, but _TALLY is zero, then no records were updated, and you can assume that the problem in this case was an update conflict.

In summary, the simple way to solve this problem is to add the following code to the AfterUpdate method on the CursorAdapter class:

   LPARAMETERS cFldState, lForce, nUpdateType, cUpdateInsertCmd, cDeleteCmd, lResult
   IF lResult AND _TALLY = 0 THEN 
     ERROR 1585 && update conflict
What is interesting here is that you will not see the error message appear on your screen; instead, the message is "trapped" by the TableUpdate call, forcing you to use the AError function to see the cause of the update failure. This occurs because the BreakOnError property was left at its default of False, meaning that errors should not cause a break. If you were to set this property to True, then the "Update Conflict" error message would appear, or if specified, your ON ERROR handler would be triggered.

This issue of update conflicts is "by design" for the CursorAdapter since there is no easy way for VFP 8 to automatically detect this problem. Therefore, this code (or similar) will probably end up in your foundation CursorAdapter classes when going against native data sources.

Thanks for your registration, follow us on our social networks to keep up-to-date