Topic: Multi-user and data buffer issues in Visual FoxPro
Sumptuous
Many foxes are coming over from DBase-FoxBase-FoxPro-VFP. If FoxBase to FoxPro is a leap, then from FoxPro to VFP is a sublimation. Two big changes on the long programming road are accompanied by the excitement and adaptation of the upgrade, usual thinking often makes us easily ignore the new version of the new content. Let's take a look at the example of editing records in the form:
In the FoxPro 2.x era, I designed this:
1. Place the text box (Text) control corresponding to the field in the table on the screen to store the corresponding memory variables (such as M.cust_IDT and M.NAME, etc.);
2. When the user positions the pointer of the table to a certain record (for example, pressing the "Next Record" button), use the Scatter MEMVAR statement to pass all the fields of the record to the corresponding memory variable, and use the show get Refresh the value displayed in the text box on the screen. At this moment, the user cannot edit these variables (at this time, the text box is set to not available (Disabled), or its WHEN clause returns .f. Value), because the user is in the "browsing" state.
3. When the user selects the "Edit" button, the program locks the record (if the prompt is displayed, then the prompt information is displayed), then detect each field value and the corresponding memory variable value (if there is different, the description must have Other users modified and saved records after we entered the editing state, in which case the prompt information is displayed, then refresh with Scatter Memvar and SHOW GET statement so that the user will see the current value in the record;
4. If all fields and corresponding memory variables completely match, set the text box controls in the variable to be available (enabled) or let the WHEN clause returns .t. So allowing users to edit these variables;
5. When the user selects the "Save" button, the input data is checked according to a certain rule, and then writes the memory variable back to the record with the Gatter Memvar statement, and unlocks the record unlock, and put all the memory. The text box where the variable is located is not available (Disabled), or lets its WHEN clause returns .f. Value, at this time, the user returns to the "browsing" state.
Please note that we have not directly read and write records in this process, but first assume the fields to the same name memory variable, let the user edit these memory variables, if everything is correct, write these memory variables back to the record. This method is mainly for protection tables. If the verification rule is not met, the data is not allowed to be returned to the table. Another point to note is that when the user edits the record, lock (LOCK), which prevents other users from editing the same record at the same time. However, this method has a big shortcoming: assuming that a user begins to edit a record, the record has been in a lock state before he pressed the "Save" button. If the user has something to go out, such as lunch, then Other users cannot edit the L.
Of course, you can lock when you enter the "edit" state, but only to lock before the "save" record, then unlock it immediately, so that the record is lowered in the shortest time, so that other users have sufficient time to edit the record. . But this also has a shortcoming. Imagine: If the user edits the memory variable, then click "Save", if other users have edited this record before you click "Save", and have not yet saved, what happens at this time? ? Do you save your changes? Want to give up your changes? These are all what we have to consider carefully when designing. We are interested in designing this, and its purpose is to protect data. If you write the program just you use, it will be more simpler: You can directly read the fields in the record, such as you can directly in the screen, so that you entered directly, write directly into the record . However, we cannot guarantee what end users can be clearly entered what you can't enter. We have to create a "firewall" between users and data sheets to protect data. Create such a "firewall" in FoxPro 2.x to write a lot of code!
It is worthy of our happiness that VFP provides built-in "firewall" mechanism, which has two roles: one is to read the record directly, and the other is only allowed by all the inspection rules to be written back. This mechanism is buffering.
buffer
In the example we talked about, it was stored through memory variables. This method can be considered manually establishing a data buffer. By transmitting data from the record to "buffer" by using Scatter Memvar, Use Gather Memvar from "buffer" back to record.
The VFP not only automatically performs a single record buffer (called row buffer or recording buffer), but also supports another type of buffer, that is, a table buffer, and a table buffer can access a plurality of records by buffer. When the row buffer is generally used in an access to a record, this mechanism is generally applied to data entry, as mentioned above: The user can display or edit a single record in the form. The table buffer is suitable for updating multiple records, such as a detailed entry screen of the order, by using a table buffer on the item chart, allows the user to edit the number of detailed records, and save all detailed records or give up all details .
In addition to two buffer mechanisms, VFP has two locking mechanisms. The above-described way of locking in FoxPro 2.x is called a conservative lock (or pessimistic lock method) - is unlocked when the user selects "edit" until the user selects "saving" and unlock it. . This locking mechanism ensures that other users cannot modify the record when the user modifies the record, but so is advantageous, depending on the situation. Another locking method of the foregoing is called an open lock (or optimistic lock method) - only the record is locked only when writing recording, and then unlock it immediately. This lock mechanism allows the records that can be used in the maximum time of the record, but we must handle conflicts caused by two users simultaneously edit the record. As we will see below, this is really simple for VFP, and the open buffer mechanism is selected in most cases in VFP.
Because records can be automatically buffered in VFP, there is no need to use the "artificial buffer" mechanism, in other words, we can now read the fields in the record without having to set up memory variables for each field. To save your modification, we will simply tell VFP to write the contents in the buffer to the table, if you cancel the modification, tell the VFP to not write. If we will see how this is operational. When a table is turned on, VFP creates a "temporary table" as a buffer, this "temporary table" is used to define the properties of the table. For local tables, the only attribute of "temporary table" is used to define a buffer mode, and there are other properties other than the scope of this article, and the values of these attributes are set, used by CursorsetProp () function. CURSORGETPROP () gets. We will see how to use these functions.
When adding records, the table buffer has an interesting feature: as the record is added to the breaker, they are given a negative record number, the first addition record, the return value is -1, the second The return value is -2, and so on. You can use a negative GO command to locate the additional records in the buffer. This is useful when dealing with the record number, such as whether the variable INRECNO is a available record number, in the buffer state, to detect Between (INRECNO, 1, Recuit ()) or Inrecno <0 without only Between (Inrecno , 1, Reccount ()).
Use buffer
By default, the buffer is closed. In this case, the VFP and FoxPro 2.x are the same when updating the table. To use buffer you must open it. Buffling the table for free tables and databases. To use the buffer to set MultiLocks ON, because the multiLocks default is OFF, if you forget to set its value ON, an error message will appear. You can add multiLocks = ON to the config.fpw file, or save its value on with the "Option" feature under the "Tool" bar.
We define buffer mode via CursorsetProp ('Buffering',
Buffer and lock method
No buffer 1
Conservative buffer 2
Open line buffer 3
Conservative table buffer 4
Open table buffer 5
For example, to set the current table to an open line buffer, use CursorsetProp ('Buffering', 3), to obtain a buffer mode of the currently open table, with CursorgetProp ('buffering').
To set a buffer mode of a form, you can define all all use tables in the LOAD event () in the form of the form, but the best way is to direct the BufferMode property of the form to decide whether it is open or conservative (default The value is "no"), so that the form will automatically use the table buffer to the table bind to the grid (Grid), and use the row buffer to other tables. If your form uses a data environment, you can set buffer on a table's buffermodeOverride property to replace the buffermode properties of your form.
If a user is modifying data in the buffer record (at this time the user is in the "editing" state), you can not only get the value they enter each field, but also get the initial value of each field and the current value (this The value is the actual value in the disk), providing this VFP provides an OldVal () and Curval () functions. To get: use:
User input value (buffer data)
The value before the user did not do any changes OldVal ('
The value in the current record CURVAL ('
Note: Curval () and OldVal () are only used for open buffering.
You may not understand what the curval () return value and the oldval () return value are different. If it is in a single user program, there is no difference between the two, but if it is in the network, it is very When another user edits a record, another user also edits the same record and "saved" before "saving" in this user, and is an example:
Zheng will locate the Contacts.dbf pointer to the 2nd record and click the "Edit" button:
Field buffer value initial () current value Curval ()
Name Li Da Li Da Li Da
Company Name Fover Technical Development Company Fover Technical Development Company Fover World Technology Development Company
Then, Zheng will change the company name to "Fox Temple", but have not yet saved records:
Field buffer value initial () current value Curval ()
Name Li Da Li Da Li Da
Company Name Fox Temple Club Fover Technical Development Company Fover Technology Development Company
At this time, the Contacts.dbf pointer is also recorded in the second record, and click the "Edit" button, he changed the company name as "Hunting Fantasia Club" and saved. At this point, you will see the following results on the machine's machine:
Field buffer value initial () current value Curval ()
Name Li Da Li Da Li Da
Company Name Fox You Club Fover Technical Development Company Hunting Fantasy Club
Note: Contacts in the above table, company name, OldVal ('company name'), and Curval ('company name') will return different values. Access the initial value, buffer value, and current value of each field in the record, you can:
l Determine which fields are modified by the user by comparing buffer values and initial values;
l After the initial value and the current value are detected, there is another user in the network to modify the same record. If you don't care about the initial value and the current value, just want to detect whether the content in a field is modified, you can use the getfldState () function. This function returns a value and points to whether the current record is modified. GetFldState () calls in the following format:
GetfldState (
The return value and its significance are shown in the following table:
Return value meaning
1 did not change
2 fields are edited or recorded by delete tags
3 Add a new record but no edit field, and the deletion record of the record does not change
4 Add a new record and edit the field, or the recorded delete tag is changed
The recorded removal tag is changed, refers to the deletion of records or recovery (RECAL) records. It is worth noting that it is immediately recovered immediately after the record is deleted, although there is no impact on the record, but its deletion tag is changed, and therefore, the getfldState () function returns 2 or 4. If you don't define an alias or workspace, getfldState () will operate the currently open table. The
Taking the situation we mentioned earlier, when Zheng edited the 2nd record, getfldState (-1) will return "112", the first number "1" description record does not add or delete, the second number " 1 "Description The first field (name) has not changed, and the third number" 2 "describes the second field (company name) content changes.
Write back
We have just continued to just. Now, Zheng Zheng hits the "Save" button, how should we write the data in the buffer (update table)? For row caches, the table will be updated when you move the record pointer or call the tableupdate () function. For table buffering, the mobile recording pointer does not cause updates to tables (because it is multi-recorded by simultaneous buffer), so usually only the tableUpdate () function can only be updated. For row buffering, it is best to use the tableUpdate () function because this better controls the process of the program.
If the content in the buffer is properly written to the record, TableUpdate () returns .t. Value, if the recording buffer has not changed (the user does not edit any field, add a record or change the record of the record), at this time, TableUpdate () Also return .t., Although there is no doing anything.
TableUpdate () can take several parameters:
TableUpdate (
The first parameter indicates which records are updated: Set to .f., Only update the current record, if .t., Update all records (only affects the table buffer only). If the second parameter is .t., Any other user's modification will be overwritten by the current user's modification. If the third parameter is not defined, TableUpdate () will update the current table.
How to cancel the user's modification? For methods with memory variables, you can revert to memory variables from the data of the Scatter MEMEVAR statement again, and for buffering, the same function can be achieved with a TableRevert () function.
Error handling
We continue to "Zheng Mou and in a" example. When Zheng is clicking on the "Save" button, the code will execute the tableUpdate () function to write the data in the buffer to record. Keep in mind that when Zheng edited a record, I have modified the same record and save it. When Zheng hits "save", TableUpdate () will return .f., Indicating why it can't write buffer write records?
VFP cannot write buffer to record in the following cases:
l When a user edits records, other users have modified and saved the record (as in our example). The VFP automatically compares the OldVal () value of each field, and the Curval () value, if any difference is detected, a conflict occurs. l The user enters a repeated main index or candidate index value.
l Violating a field or table verification rule or does not support NULL fields.
l A trigger failed.
l Other users are locked the record.
l Other users delete the record.
When TableUpdate () fails, we must decide what to do next, and if your program allows the user to click "Next" or "Previous" button while editing records, the two buttons do not call TableUpdate. If you have to handle errors that will happen when saving. In both cases, the program is specified to an appropriate location is the error trap handler.
Multi-user and data buffer problems in Visual FoxPro (below)
-------------------------------------------------- ------------------------------
2000-10-6 17:07:00
Error handling in VFP has been improved. Previously handled the error traps (you can still continue using these methods in the VFP) is when the error occurs with an on error command to determine the program to be executed, the typical error handler is to view Error () and Message () to determine What happened, then take a corresponding action.
Now VFP provides an automatic error handling mechanism: it is an Error method. If an Error method in a control or form is defined, it is automatically executed when an error occurs. AERROR () is a new increase in VFP, by passing a parameter, the function can create or update an array containing the following elements
Element type description
1 Digital error number (same as Error ())
2 Character error message (same as Message ())
3 characters If there is an error message parameter (the same as SYS (2018)), it returns (for example: a field name), no, return .null.
4 Digital or characters have a wrong work area. If not, return .NULL.
5 Digital or Character If a trigger fails, return the trigger number (inserted to 1, updated to 2, delete it is 3), if not, returns .null.
6 Digital or characters. Null. (Applicable to OLE and ODBC error)
7 numbers. Null. (Applicable to OLE error)
For example: AERROR (Iaerror) creates or updates a array called IaError.
The following is some errors that the VFP may happen when the buffer is written.
Error number # error message description
109 Record is being used by other users
1539 The fifth element of the 1539 trigger failure detection array can be determined which trigger failed
1581 Field Does Not Accept Null Value (NULL) The third element of the detection array can determine which field caused by errors
1582 Verify rules of the field can determine which field caused errors
1583 Violation of the recorded verification rules
1585 Record has been modified by other users
1884 Violation of the uniqueness detection array of indexes can determine which index marker caused by errors
For the above errors, most of them can directly process: prompt the user problem, then let the user change the error or cancel the operation in the "Edit" state. For the # 1585 error (records have been modified by other users), there are several ways to handle errors: l You can prompt the current user to modify the record and use TableRevert () to cancel the content of the current user. I think most people will not be happy to this way.
l You can use TableUpdate (.f., .t.) to enable the current user's modification to overwrite other users. In doing so, the current user is naturally happy, but other users may be dissatisfied. ()
l Copy an identical form (which is very easy to create multiple instances on the same form in the VFP), displaying other users' modifications to the record, so that the current user can decide whether to save or save other users Modify, that is, you can use TableUpdate (.f., .T.) To cancel the editing.
Have a better solution to test whether we have encountered "real" conflicts, so-called "real" conflicts, it means that two users have modified the same field at the same time. If they modify the two different fields of the same record, we can only update the current user modified fields, and retain the field modified by another user, making it unaffected. For example, in a order processing system, a user modifies the product introduction, and the other user is ordering under the product, and the number is entering the number, and the two modifications are independent of each other, and there is no conflict. At this time, we don't have a newer The records, but only the field you modified, so that two users are satisfied.
The following is the idea of achieving this idea:
l Find out the fields of OldVal () and Curval (), if any, indicating that the field has been edited by other users, if the buffer value of the field is the same as OldVal (), indicating that the current user does not modify the field. In this case, we can pass the value of Curval () to the buffer value, update, so that the buffer value can be overwritten.
l Find the buffer value and the OldVal () different fields, these fields are the current user modified fields, if oldval () is equal to Curval (), indicating that other users have not changed the field, in which case we can rest assured Override it.
l If we find the field, its buffer value is different from OldVal (), but is the same as Curval (), this shows that two users have done the same resolution. This situation seems to be unlikely, actually happening. For example, some people have sent a company's address change information, and two users simultaneously decide to update the company's address in the record. Because what they do is the same, we can override the other. However, if you update a value in the same number (such as two orders at the same time, and enter the same number), you cannot easily overwate the field, and you should see this as a "real" conflict.
l If the buffer value of a field is different from OldVal () is also different from curval (), and OldVal () and Curval () are also different, this shows that both users have modified the same field, and the value is different. . This situation is the real conflict we have encountered, we have to decide how to deal with this conflict.
When manually entering the quantity or account balance, there is an impact on the value of other users input to the buffer value. For example, if OldVal () is 10, Curval () is 20, indicating that other users have added 10; if the buffer value is 5, the current user should reduce the amount in the original basis (10), so The new buffer value should be Value OldVal () - Curval (), that is, equal to 15, should be used to update the field. For the conflict of the date field, consider business rules or processed according to actual conditions. For example, in the Patient Appointment Time program, there is a field that saves the patient's next time to make a doctor's time. If the field conflicts, then the date close to the current date is probably correct. Of course, if one of the reservation days is better than the current date (already expired), then the date should be replaced.
Other types of fields, especially characters, and note fields, usually, if you do not ask users to decide whether to override other users or cancel your modifications, the conflict is not well resolved. Only when the user saw any other user in the screen, he could make correct judgment.
The following is some code to solve the conflict (these code assumes that the error code has been detected to be # 1585, that is, the record has been modified by other users.)
* Detect each field to see which conflict has occurred.
Llconflict = .f.
For lnu = 1 to fcount ()
Lcfield = Field (LNI)
Llotheruser = OldVal (lcfield) <> Curval (LCField)
LLTHISUSER = Evaluate (LCField) <> OldVal (LCField)
LlsameChange = Evaluate (LCField) == Curval (LCField)
Do Case
* Other users have edited this field, and the current user is not edited, so use the new value directly.
Case Llotheruser and not llthisuser
Replace (LCField) with curval (LCField)
* Other users do not edit this field, or both do the same modification, so we don't have to do anything.
Case Not Llotheruser or LlsameChange
* Two users modified this field with different values.
Otherwise
Llconflict = .t.
Endcase
Next LNI
* If a conflict has occurred, processing it!
IF llconflict
Lnchoice = MessageBox ('Another User Also Changed THIS' ;
'Record. Do you want to overwrite their changes (yes),'
'Not Overwrite But See Their Changes (No), or Cancel' ;
'Your Changes (Cancel)', 3 16, 'PROBLINGM SAVING RECORD!')
Do Case
* Overwrite changes in other users.
Case lnchoice = 6
= TableUpdate (.f., .t.)
* View other users' modifications by generating a form instance.
Case lnchoice = 7do form myform name oname
* Cancel the current user's modification.
Otherwise
= TableRevert ()
Endcase
* If there is no conflict, it is forcibly updated.
Else
= TableUpdate (.f., .t.)
Endif llconflict
Table buffer writing
As we have already talked, we can use tableupdate (.t.) To write all records in the table buffer to disk. Like the row, if other users modify the table (or what else anything wrong) and not correctly update the table, TableUpdate (.t.) Will return .f. Value.
The error handler that is described above is working well in row buffer mode, because we only care about single record at some moment. But for the table buffer, we have to consider each record because there may be both modified records in the buffer, and how do we know which records we know? If you fails with TableUpdate (.t.) (Returning .f.), The situation is more complicated: We don't know which record! And some records may have been "saved", so there is no conflict with a record. Please don't worry about :), the VFP new function getNextModified () can accurately tell the information we want to know: This function returns the next record number that is modified record. If the return value is 0, it is noted that there is no modified record in the buffer. This function receives two parameters: The first parameter is a record number, which is from this record number to find the next modified record; the second parameter is the work distinguished name of the lookup. Most, you should pass 0 to the first parameter, so getnextModified () will find the first modified record, if you want to find a modified record, just pass the current record number to the first A parameter is clicking.
Below is the improved code based on the procedure just handled, it is used to process the operation when the table buffer update fails.
* First find the first modified record.
Lnchanged = getNextModified (0)
Do While Lnchanged <> 0
* Move the record pointer and try to lock it.
Go lnchanged
IF rlock ()
* Detect each field to see which conflict has occurred.
Llconflict = .f.
For lnu = 1 to fcount ()
Lcfield = Field (LNI)
Llotheruser = OldVal (lcfield) <> Curval (LCField)
LLTHISUSER = Evaluate (LCField) <> OldVal (LCField)
LlsameChange = Evaluate (LCField) == Curval (LCField)
Do Case
* Other users have edited this field, and the current user is not edited, so use the new value directly.
Case Llotheruser and not llthisuser
Replace (LCField) with curval (LCField)
* Other users do not edit this field, or both do the same modification, so we don't have to do anything.
Case Not Llotheruser or LlsameChange
* Two users modified this field with different values.
Otherwise
Llconflict = .t.
Endcase
Next LNI
* If a field conflict occurs, we can handle it here. It is different from the row. We can now do not deal with it, because all records will be written and will be handled. IF llconflict
Lnchoice = MessageBox ('Another User Also Changed');
'Record' LTRIM (STR (Lnchanged)) '. Do you want to' ;
'OverWrite Their Changes (YES), NOT OVERWRITE BUT SEE' ;
'THEIR CHANGES (NO), or CANCEL YOURES (CANCEL)?', 3 16,
'Problem Saving Record!')
Do Case
* If you have a modification overwritten other users, you can do not process because it will be updated in the future.
Case lnchoice = 6
* View other users' modifications by generating a form instance.
& nb
Reprinted!