Originally published in FoxTALK May 1999
Buffering, The Vampire Slayer
The continuing story
Last month you saw the buffering technology in Visual FoxPro. The various buffer modes were reviewed and a comparison with the FoxPro 2.x methodology of indirect edits and Visual FoxPro buffering was presented. This month you will see the TableUpdate and TableRevert functions and understand how they are used with data buffering.
So, Where’s the data really hanging out?
With buffering enabled, Visual FoxPro tracks three versions of our data. The first is the data as it exists in the disk file itself, the second is the data as it exists in our data buffer, and the last one is the data as it was when we started to edit our buffer. All of these versions of the data are available to us programmatically. We will see how we get at them later.
Last month you saw that with data buffering enabled the editing that goes on is happening to a copy of the data. The obvious question is, how do you get the copy to update the actual table? Enter the TableUpdate function.
One definition will be helpful to the discussion, dirty buffer. A dirty buffer is a data buffer that contains pending changes that have not yet been updated to the table.
To update or not to update…
TableUpdate is the function that causes the data in a buffer to be written to the file. This function takes four (4) arguments. Its syntax is listed below;
TableUpdate([<nRecords|lRecords>[, <lForce>]] [, <cAlias|nWorkArea>][, <cErrorArray>] )
As you can see, all of the arguments for this function are optional. Issuing a TableUpdate() without any arguments will update only the current record in the currently selected work area and will not force past an update conflict. Let’s look at the arguments in detail.
In Visual FoxPro 3.0 the first argument was logical and a .T. meant that all edited records should be updated while .F. meant only the current record should be updated. This logical argument still works in Visual FoxPro 6.0, but the numeric alternative gives you a higher level of control over this.
The numeric argument can be 0, 1, or 2 and it controls how the records will be updated. An argument of 0 (the default) will update only the current record. Using 1 or 2 will update all edited records. The difference between 1 and 2 has to do with what happens if one or more of the edited records cannot be updated.
Using 1 will cause the tableupdate to fail if any edited record cannot be updated. While using 2 causes Visual FoxPro to update whatever records it can and to keep a list of those records that could no be updated (this list is stored in the array passed with the fourth argument).
This logical argument controls whether or not Visual FoxPro will fail the TableUpdate if the record being updated has been changed by another user while the edit was going on. A value of .T. forces the update through an update conflict, while .F. will cause the update to fail if the record on disk is not the same as the record the edit started with.
It is strongly advised that this argument always be set to .F.. The reason is that if you don’t then your code will be overwriting the edits of other users indiscriminately. What you really want to do it to check the return value of TableUpdate to see if it succeeded or not and respond to that occurrence.
This argument conveys the name of the work area that the tableupdate should affect. It can either be the alias name of the work area or the work area’s number (does anyone still use work area numbers in their code?).
This is another of the optional arguments that is recommended to be used at all times. If this argument is omitted then the TableUpdate will affect the currently selected work area. While this situation can be manageable, it is recommended that you tell TableUpdate what work area to affect and not depend on the proper work area having been selected. The reason is that Visual FoxPro is event driven and you may not be able to depend on which work area is current because different event sequences may affect the currently selected work area. By providing the alias with the Tableupdate function all you eliminate any possible side effect of the wrong work area being updated.
This last argument only applies if the first argument is 2. With a first argument of 2, TableUpdate will attempt to update all edited records and if any record fails the record number will be added to the array named in the cErrorArray argument.
An example of values of 1 and 2 in the first argument may help to understand what this all means. Assume that records 1, 3, 5, 6, and 9 have been edited in a buffer that is table buffered. We issue a TableUpdate() with either 1 or 2 as the first argument and there is an update conflict on records 3 and 6.
With the argument of 1, TableUpdate will update record 1 and then when it cannot update record 3 it will stop trying to update records and return .F. as a failure of the update operation. Using 2 s the first argument will cause TableUpdate to continue attempting to update the rest of the record after record 3 failed. The array named in the fourth argument will hold the list of records that could not be updated (3 and 6), the TableUpdate will still return .F. indicating a failure of the update because the update was not completed.
…Not to Update
Data buffering not only allows us to save the users edit, it also provides a way of discarding the edit. The TableRevert() function will discard a dirty buffer and restore the values to what is currently stored on the disk. The syntax for TableRevert is shown below;
The two arguments for this function are described in the next sections.
This logical argument controls which records will be reverted. If the first argument is .T. then all dirty records will be reverted in the selected alias, a value of .F. will revert only the current record. If this argument is not specified then only the current record will be reverted.
The alias name or work are number that the TableRevert() function should affect. If it is not explicitly used then the current selected work area will be affected.
How is the vampire slain?
The TableUpdate and TableRevert functions return values that indicate their success or failure. TableUpdate returns a logic value with .T. indicating that the update was completely successful and .F. meaning that some or all of the update failed. TableRevert returns a number indicating the number of records that were reverted.
In either case it is important to recognize the return values and react to them. For example, the following code could be used to update a buffer of the Customer table.
IF NOT TableUpdate(1,.F.,”Customer”)
Wait Window “Unable to save you work”
The IF statement is checking to see the return value from TableUpdate and then responding accordingly. The TableRevert is necessary because a failed TableUpdate does not update the table, but it leaves the buffer dirty and that buffer must be cleaned before you can close the table. There are other ways to handle the failed TableUpdate, for example;
IF NOT TableUpdate(1,.F.,”Customer”)
IF MessageBox(“Unable to save your work. Would you like to discard it?”,;
MB_YESNO,”Failed Update”) = IDYES
In this case the failed table update would cause the user to get a message box asking them if they want to discard or not. If they choose yes then the table is reverted otherwise they are placed back in the edit form with their work intact. They could try to save again at a later time.
Notice in both examples that there is no work area selection going on. This is because the work area is specified in the functions thus removing any need to explicitly select any particular work area.
Last month we examined data buffering in general and found out some of the things it can do for us. We saw that buffering can make our code much simpler and easier to understand.
This month the two major functions affecting buffering were explored, TableUpdate() and TableRevert(). These two functions give us ultimate control over when and if the edited buffer will be written to disk.