Multi-user and data buffering in Visual FoxPro

Source: Internet
Author: User
Tags add error code error handling functions numeric value ole variables variable
visual| Data | questions
Topics: Multiple users and data buffering issues in Visual FoxPro

Shaoxing

Many Fox friends (foxers) are from DBASE―FOXBASE―FOXPRO―VFP such a road come over, if say from FoxBase to Foxpro is a leap, then from the Foxpro to VFP is a sublimation. The two major changes in the long programming path are accompanied by the thrill of escalation and the pain of adaptation, which often makes it easy for us to ignore new versions of content. Let's take a look at the following example of editing a record in a form:

In the age of FoxPro 2.X, I was designed like this:

1, on the screen and the table in the corresponding text box control, used to store the corresponding memory variables (such as M.cust_idt and m.name, etc.);

2. When a user navigates a table's pointer to a particular record (for example, by pressing the "Next Record" button), it uses the scatter Memvar statement to pass all the fields of the record to the corresponding memory variable and then uses show get to refresh the value displayed in the text box on the screen. At this point, the user cannot edit the variables (the text box is either set to unavailable (Disabled), or the When clause returns. F. Value) because the user is in a "browse" state.

3, when the user selected the "Edit" button, the program locks the record (displays the prompt if it cannot be locked), and then detects each field value and the corresponding memory variable value (if there is a difference, it must be that other users have modified and saved the record after we entered the edit state, in which case, To display the prompt, then refresh with the scatter Memvar and show get statements so that the user sees the current value in the record;

4. If all the fields match the corresponding memory variables, set the text box control in which the variable is available (Enabled) or let the When clause return. T., so that users can edit these variables;

5, when the user selects the "Save" button, the input data is checked according to certain rules, then the memory variable is written back into the record with the Gatter Memvar statement, the record is unlocked (unlock), and then the text box with all the memory variables is set to unavailable (Disabled). or let the When clause return. F. Value, at which point the user is back in the "browse" state.

Note that in this process we do not directly read or write records, but first assign each field value to the same memory variable with the same name, and then let the user edit these memory variables, if it is normal to write these memory variables back to the record. This method is used primarily to protect the table and does not allow data to be stored back into the table if the validation rules are not met. Another point to note is that when a user edits a record, the record is lock (lock), which prevents other users from editing the same record at the same time. However, there is a big drawback to this approach: Suppose a user starts editing a record, the record is locked until he presses the Save button, and if the user is temporarily out of the box, such as lunch, other users may not be able to edit the record.

Of course, you can not enter the "edit" state when the lock, and only the "save" before the record lock, save the lock immediately, so that the record can be locked for the shortest time, so that other users have sufficient time to edit the record. But there are drawbacks, just imagine: if the user edited the memory variable, then click "Save", but if other users before you point "save" also edited this record, and has not been saved, then what will happen? Do you want to overwrite other people's changes in your save? Do you want to give up your changes? These are the issues that we have to consider carefully at design time.

We designed it so carefully that it was meant to protect the data. If you write a program that you use alone, it may be much simpler to design: You can directly read the fields in the record, such as you can browse a table directly in the screen, so that you enter the content directly into the record. However, we cannot guarantee that the end users are as aware of what they can enter as you do, and that we have to create a "firewall" between the user and the datasheet to protect the data. Create such "Firewalls" in FoxPro 2.X to write a lot of code!

We are pleased that VFP provides a built-in "firewall" mechanism, which has two functions: one is the direct reading of records, and the other is only allowed through the data of all the test rules are written back. This mechanism is a buffer.

Buffer

In the example we have just mentioned, the memory variable is used to store the contents of the record, which can be considered as a manual build of a data buffer, through the use of scatter memvar to transfer data from the record to the "buffer", and then gather Memvar from the "buffer" back to the record.

VFP can not only automatically make a single record of the buffer (called row buffer or record buffer), but also support another type of buffer, that is, table buffer, table buffer access to multiple records through the buffer. Row buffering is generally used for data entry when one record is accessed at a time, as mentioned earlier: users can display or edit a single record in a form. Table buffering is applicable to a number of records, such as an order form of the detailed entry screen, through the list of items using a table buffer, you can allow users to edit more than one detail record, the last one-time all the details of the record to save or give up.

Besides two kinds of buffering mechanism, VFP also has two kinds of locking mechanism. The method of locking in the FoxPro 2.X, described above, is called a conservative locking method (or pessimistic locking method)--When the user chooses "edit" to lock, until the user chooses "save" and then unlocks it. This locking mechanism ensures that when the user modifies the record, no other user can modify the record, but it has pros and cons, depending on the circumstances. Another type of locking method described above is called open lock (or optimistic locking)--the record is locked only when the record is written back, and then unlocked immediately. This locking mechanism makes it possible to record the maximum time available to other users, but we must deal with the conflicts that result when two users edit the record at the same time. As we will see below, this is really too easy for VFP, in VFP Most of the cases are selected open buffer mechanism.

Because the records in VFP can be automatically buffered, so there is no need to use the "manual buffering" mechanism, in other words, now we can directly read the records in the field without concern for each field set memory variables. To save changes, we simply tell VFP to the buffer in the contents of the table can be, if the cancellation of changes, told VFP can not write. We'll see how this is done in a moment.

When you open a table, VFP creates a "temporary table" (cursor) as a buffer, which is used to define the properties of the table. For the local surface, the unique properties of the temporary table are used to define buffering, and the views and remote tables have other attributes that are outside the scope of this article, and the values are set by the Cursorsetprop () function and are obtained by Cursorgetprop (). We'll see how to use these functions later.

When you append records, table buffering has an interesting feature: As records are added to the retarder, they are given a negative record number, the first record added, the RECNO () return value is-1, the second return value is-2, and so on. You can locate the appended record in the buffer with a go command with a negative number. This is useful when working with record numbers, such as to verify that the variable INRECNO is an available record number and, in a buffered state, to detect between (Inrecno,1,reccount ()) OR inrecno<0 and not just between ( Inrecno,1,reccount ()).

Using buffering

The buffer is closed by default, and in this case VFP and FoxPro 2.X are the same when updating the table, and you must open it to use buffering. Buffering applies to tables in the free table and database. To buffer also set multilocks on, because the multilocks default is off, and if you forget to set its value to on, an error message appears. You can add Multilocks=on to the Config.fpw file, or save the value on by using the Options feature under the Tools bar.

We define the buffering mode by Cursorsetprop (' Buffering ',<n>,<alias>). If you are setting a buffer for the current table, you do not have to define &LT;ALIAS&GT, depending on the buffer and locking method you want <n> for the following values:

Buffering and Locking method
<n> value

No buffer
1

Conservative line Buffering
2

Open row buffering
3

Conservative table buffering
4

Open Table buffering
5


For example, to set the current table to open row buffering, use Cursorsetprop (' buffering ', 3) to get the buffer of the table currently open, using Cursorgetprop (' buffering ').

To set a form's buffering style, you can use Cursorsetprop () to define all the tables used in the form's Load event, but the best way is to set the BufferMode property of the form directly to determine whether it is open or conservative (the default is "None"), so after setting, The form automatically uses table buffering for the tables bound to Grid (grid), and row buffering for other tables. If your form uses a data environment, you can set a buffer for the Buffermodeoverride property of a table to replace the BufferMode property of the form.

If a user is modifying the data in the buffered record (the user is in the edit state), you can not only get the values they enter into each field, but also get the initial value and current value of each field (this value is the actual value on the disk), which provides the Oldval () and Curval () functions.

To get:
Use:

User input values (buffered data)
<fieldname> OR <alias.fieldname>

Value before the user makes any changes
Oldval (' <fieldname> ')

Values in the current record
Curval (' <fieldname> ')


Note: Curval () and Oldval () are used only for open buffering.

You may not understand the difference between the return value of the Curval () and the return value of the Oldval (). If it's in a single-user program, there's no difference between the two, but if you're on a network, in an open lock, it's very likely that when the user edits a record, another user edits the same record, and "Save" before this user "saves", here is an example:

Zheng MoU position the contacts.dbf pointer to the 2nd record, and click on the "Edit" button:

Field
Buffering value
Initial value Oldval ()
Current Value Curval ()

Name
Li Da's
Li Da's
Li Da's

Company Name
Fox Friends Technology Development Co., Ltd.
Fox Friends Technology Development Co., Ltd.
Fox Friends Technology Development Co., Ltd.


Then, Zheng MoU will change the company name to "Fox Friends Club", but has not kept the record:

Field
Buffering value
Initial value Oldval ()
Current Value Curval ()

Name
Li Da's
Li Da's
Li Da's

Company Name
Fox Friends Club
Fox Friends Technology Development Co., Ltd.
Fox Friends Technology Development Co., Ltd.


At this point, the CONTACTS.DBF pointer also navigates to the 2nd record and clicks on the "Edit" button, and he changes the company name "Fox Hunter Club" and saves it. At this point in Zheng's machine will see the following results:

Field
Buffering value
Initial value Oldval ()
Current Value Curval ()

Name
Li Da's
Li Da's
Li Da's

Company Name
Fox Friends Club
Fox Friends Technology Development Co., Ltd.
Fox Hunters ' Club


Note: Contacts in the table above. Company name, Oldval (' Company name '), and Curval (' Company name ') will return different values. To access the initial values, buffering values, and current values for each field in the record, you can:

l to determine which fields have been modified by the user by comparing buffer values and initial values;

L Detect whether other users in the network have modified the same record after beginning editing by comparing the initial value with the current value. You can use the Getfldstate () function if you don't care about the initial value and the current value, but just want to detect if the content in a field has been modified. This function returns a numeric value indicating whether the current record has been modified. Getfldstate () is called in the following format:

Getfldstate (<fieldName> | <FieldNumber> [, <Alias> | <WorkArea>])

The return value and its meaning are shown in the following table:

return value
Significance

1
No change.

2
The field is edited or the record deletion mark is changed

3
A new record was added but no fields were edited, and the record's delete record did not change

4
A new record was added and the field was edited, or the deletion mark of the record was changed


The deletion mark of the record is changed to refer to the Delete record or recovery (recall) record. It is worth noting that the recovery of the record immediately after the deletion, although it did not affect the record, but its deletion tag has been changed, so the Getfldstate () function will return 2 or 4.

If you do not define an alias or workspace, Getfldstate () operates on the currently open table. The <FieldNumber> definition is 0, which returns the addition and deletion status of the current record, and if defined as-1, returns a string in which the first number reflects the state of the entire table, and each subsequent number is returned to the status of each field.

For example, as we mentioned earlier, when Zheng edited 2nd record, Getfldstate (-1) will return "112", the first digit "1" indicates that the record was not added or deleted, and the second digit "1" indicates that the first field (name) has not changed. The third number "2" indicates that the second field (company name) content has changed.

Write-back of buffered records

We have also continued with the example just now. Now suppose Zheng clicked on the "Save" button, how should we write the data in the buffer to the record (update the table)? For line buffering, the table is updated when you move the record pointer or call the TableUpdate () function. For table buffering, moving the record pointer does not cause the table to be updated (because it is a multiple-record buffer), so it is usually only possible to call the TableUpdate () function to update the table. It is best to use the TableUpdate () function for line buffering because it is better to control the whereabouts of the program.

If the contents of the buffer are correctly written to the record, TableUpdate () returns. T. Value, if the record buffer does not change (the user does not edit any fields, add records, or change the deletion state of the record), TableUpdate () also returns. T., although actually nothing has been done.

TableUpdate () can take several parameters:

TableUpdate (<AllRows>,<Forced>,<Alias>|<Workarea>)

The first parameter indicates which records are updated: set to. F., only the current record is updated, and if. T, all records are updated (only the table buffers are affected). If the second argument is. T., any modifications to other users will be overwritten by the current user's modifications. If the third argument is not defined, tableupdate () updates the current table.

How do you cancel the changes made by the user? For a method that uses memory variables, you can again restore the data from the disk to the memory variable with the scatter Memevar statement, and for Buffering, the Tablerevert () function achieves the same function.

Error handling

We continue to "Zheng MoU and in a certain" example, when Zheng clicked "Save" button, the code will execute the TableUpdate () function to write the data in the buffer to the record. Please keep in mind that the same record has been modified and saved when Zheng edited the record. When Zheng clicks "Save", TableUpdate () will return. F., stating that it cannot write buffers to records, why is this so?

VFP cannot write a buffer to a record in the following situations:

l When a user edits a record, the other user modifies and saves the record (as in our example). VFP automatically compares the Oldval () value of each field with the Curval () value, and if any difference is detected, a conflict occurs.

L The user entered a duplicate primary or candidate index value.

L violated validation rules for a field or table, or null values appear in fields that do not support NULL.

L a trigger (trigger) failed.

L Other users have locked the record.

L Other users have deleted the record.

When TableUpdate () fails, we must decide what to do next, and if your program allows the user to click the "Next" or "previous" button when editing the record, and the two buttons do not call TableUpdate (), You have to deal with the errors that will occur when you save automatically. In both cases, assigning the program to the appropriate location is the wrong trap handler.

Multi-user and data buffering in Visual FoxPro (lower)

--------------------------------------------------------------------------------

2000-10-6 17:07:00


In VFP, error handling has been improved. The methods used to handle the error traps (you can still use them in VFP) are to use the On Error command to determine which program to execute when the error occurs. A typical error handler is to look at the error () and message () to determine what went wrong and then take the appropriate action.

Now VFP provides an automatic error handling mechanism: is the error method. If you define a control or an error method in a form, it is automatically executed when an error occurs. Aerror () is a new function of VFP, which can create or update an array containing the following elements by passing a parameter

Elements
Type
Describe

1
Digital
Error number (same as error ())

2
Character
Error messages (same as message ())

3
Character
If there is an error message parameter (same as SYS (2018)), it is returned (for example, a field name) and none. Null.

4
Number or character
The workspace where the error occurred. If not, it is returned. Null.

5
Number or character
If a trigger fails, return the trigger number (insert as 1, update to 2, delete to 3), or return if not. Null.

6
Number or character
. NULL. (Applied to OLE and ODBC errors)

7
Digital
. NULL. (Applied to OLE errors)


For example: Aerror (iaerror) creates or updates an array called Iaerror.

The following are some of the errors that VFP may have when it writes a buffer to a table:

Error number #
Error message
Description

109
Record is being used by another user


1539
Trigger failed
Detects the 5th element of an array to determine which trigger failed

1581
field does not accept null values (NULL)
Detecting the 3rd element of an array can determine which field is causing the error

1582
Violation of validation rules for a field
Detecting the 3rd element of an array can determine which field is causing the error

1583
Violation of validation rules for records


1585
Record has been modified by another user


1884
The uniqueness of the index was violated
Detecting the 3rd element of an array can determine which index tag is causing the error


For these errors, most can be handled directly: Prompt the user for the problem, and then let the user correct the error or cancel the operation in edit state. For a #1585 error (the record has been modified by another user), there are several ways to handle this error:

L can prompt the current user to have someone else modify the record and then use Tablerevert () to cancel the current user's edits. I think most people will be unhappy with this approach.

L can use TableUpdate (. F.. T.) To force the update of the record so that the current user's modifications overwrite other user modifications. In doing so, the current user is naturally happy, but other users may be dissatisfied. :(

L Copying a single form (creating multiple instances of the same form in VFP is very easy), this shows the other user's modifications to the record, so that the current user can decide whether to save or not save other user's changes, that is, you can use TableUpdate (. F.. T.) Force the update or use Tablerevert () to cancel the edit.

There are better schemes to detect whether we are encountering a "real" conflict, a so-called "real" conflict, where two users have modified the same field at the same time. If they modify the two different fields of the same record, we can update only the fields that the current user modifies, leaving the fields modified by another user unaffected. For example, in an order processing system, a user modified the product introduction, while another user in the order of the product, is entering quantity, the two changes are independent of each other, and there is no conflict, then we do not update the entire record, but only update the field of their own modification, so that, Two users will be satisfied.

Here's how to achieve this idea:

L Find a field that is different from oldval () and Curval (), indicating that the field has been edited by another user, and that the current user has not modified the field if the buffer value for that field is the same as Oldval (). In this case, we can pass the value in Curval () to the buffer value first, and then update it, so that the buffer value will be prevented from overwriting the new value.

L Find fields that are different from the Oldval (), which are the fields that the current user has modified, and if Oldval () is equal to Curval (), which means that other users have not changed the field, we can safely overwrite it.

L If we find a field whose buffering value is different from oldval (), but the same as Curval (), this means that two users have made the same changes to the same field. This may seem unlikely, but it will happen. For example, someone sent a company address change information, and exactly two users also decided to update the record of the company address. Because the changes they made are the same, we can overwrite the other. However, if you are updating a measure in the same quantity (for example, if two people place an order at the same time and enter the same number), you cannot simply overwrite the field, and you should see this as a "real" conflict.

If you find that the buffer value for a field is different from Oldval () and Curval (), and Oldval () differs from Curval (), it means that both users have modified the same field and the values are different. This situation is the real conflict we are confronted with and we have to decide how to deal with this conflict.

When you manually enter inventory quantities or account balances, one possibility is that other user-entered values affect the buffering value. For example, if Oldval () is detected, Curval () is 20, indicating that other users have increased the amount by 10, and if the buffer value is now 5, indicating that the current user wants to reduce the amount to 5 on the original base (10), the new buffer value should be value+ oldval ()- Curval (), which is equal to 15, should update the field with this number.

For a date-type field conflict, consider the business rules or deal with the actual situation. For example, in the "Patient Appointment Time" program, there is a field that holds the patient's next appointment for a doctor, and if the field is conflicting two dates, the date closest to the current date is likely to be correct. Of course, if one of the appointments is earlier than the current date (has expired), then the date should be taken back.

Other types of fields, especially character and Memo fields, are often difficult to resolve if you do not ask the user to decide whether to overwrite other users ' changes or to cancel their own changes. It is only when the user sees on the screen what other users have done to make a correct judgment.

Here are some of the code that resolves the conflict, which assumes that the error code has been detected as #1585, that is, the record has been modified by another user. )

* Check each field to see which conflict has occurred.

Llconflict =. F.

For lni = 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 edit the 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 did not edit the field, or both made the same changes, so we do not have to do any processing.

Case not Llotheruser or Llsamechange

* Two users modified the field with different values.

otherwise

Llconflict =. T.

Endcase

Next Lni

* If there's a conflict, deal with it!

If llconflict

Lnchoice = MessageBox (' Another user also changed this ' +;

' Record. Do your want to overwrite their changes (Yes), ' +;

' Not overwrite but their changes (No), or Cancel ' +;

' Your changes (Cancel) ', 3 +, ' Problem saving record! ')

Do case

* Overwrite other user's modification.

Case Lnchoice = 6

= TableUpdate (. F.. T.)

* View changes to other users by generating a form instance.

Case Lnchoice = 7

Do form MYFORM name Oname

* Cancel the modification of the current user.

otherwise

= Tablerevert ()

Endcase

* If no conflict occurs, force the update.

Else

= TableUpdate (. F.. T.)

endif llconflict

Table Buffered Write

As we have mentioned earlier, we can use TableUpdate (. T.) Writes all records in the table buffer to disk at once. As with row buffering, if another user modifies the table (or any other cause of error) and does not update the table correctly, TableUpdate (. T.) will return. F. Value.

The error handler described above works well in buffer mode because we only care about a single record at some point. But for table buffering, we have to consider every record, because there may be both modified and unmodified records in the buffer, how do we know which record to update? If you use TableUpdate (. T.) Failed (returns. F.), the situation becomes more complicated: we don't know which record to mistake! And some records may have been "saved", so more than one record will collide. Please do not worry:, VFP new function getnextmodified () can tell us exactly what we want to know: The function returns the record number of the next modified record. A return value of 0 indicates that no records have been modified in the buffer. This function receives two parameters: The first argument is a record number, and it is from this record number that the next modified record is searched; the second parameter is the distinguished name of the job that was found. Most of all, you should pass 0 to the first argument so that getnextmodified () will find the first modified record, to continue to find the next modified record, just pass the record number of the current record to the first parameter.

The following is an improved code based on the program that was dealing with the conflict, which is used to handle actions when the table buffering update fails.

* Find the first record to be modified first.

lnchanged = getnextmodified (0)

Do While lnchanged <> 0

* Move the record pointer and try to lock it.

Go lnchanged

If Rlock ()

* Check each field to see which conflict has occurred.

Llconflict =. F.

For lni = 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 edit the 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 did not edit the field, or both made the same changes, so we do not have to do any processing.

Case not Llotheruser or Llsamechange

* Two users modified the field with different values.

otherwise

Llconflict =. T.

Endcase

Next Lni

* If there is a field conflict, we can handle it here, unlike row buffering, we can not do it now, because all future records will be written and then processed.

If llconflict

Lnchoice = MessageBox (' Another user also changed ' +;

' Record ' + LTrim (str (lnchanged)) + '. Do your want to ' +;

' Overwrite their changes (Yes), not overwrite but ' +;

' Their changes (No), or cancel your changes (cancel)? ', 3 + 16,;

' Problem saving record! ')

Do case

* If you choose to overwrite other user's modifications, you can do this without processing because you will be updated once later.

Case Lnchoice = 6

* View changes to other users by generating a form instance.

&nb

Reproduced!




Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.