Plateforme Level Extreme
Abonnement
Profil corporatif
Produits & Services
Support
Légal
English
Articles
Recherche: 

Buffering
Hilmar Zonneveld, July 1, 2002
Introduction Buffering is a feature in Visual FoxPro that allows us to give the user "undo" and "save" capabilities. In the old FoxPro 2.x days, programmers either didn't provide this capability, or edited memory variables, and copied information between these variables and the table fiel...

Introduction

Buffering is a feature in Visual FoxPro that allows us to give the user "undo" and "save" capabilities.

In the old FoxPro 2.x days, programmers either didn't provide this capability, or edited memory variables, and copied information between these variables and the table fields with the SCATTER and GATHER commands. Nowadays, the recommended approach for this purpose is to use Visual FoxPro's built-in capabilities, namely, buffering. Another feature that is seldom required nowadays is record locking - most of the time, you will be using Visual FoxPro's implicit record locking.

If you use a commercial framework, everything related with buffering is most likely already done for you in the standard editing forms, but a general understanding of what is going on is appropriate, for debugging, and for certain tasks where you have to manipulate the tables yourself, without the aid of the framework.

Overview

The basic steps to use buffering, which I will explain in detail later, are:

  • Understand the built-in buffers
  • Choose a buffering mode
  • Open a table or view
  • Enable buffering
  • Do some changes
  • Undo changes, or
  • Save changes, and
  • Check whether the save operation was successful

I will also explain the following related points:

  • Deleting records
  • New records
  • Finding modified records
  • Checking for changes
  • Current and old values
  • Transactions

The built-in buffers

When buffering is enabled, Visual FoxPro creates two levels of buffers for each modified record. That makes a total of three different versions of each record:

  1. The record which is currently on disk,
  2. A copy of the record, as it was before the user started editing, and
  3. The record, including changes done by the current user.

Versions (1) and (2) of the record might be different if another user made changes to the record in the meantime, depending on the buffering mode.

Choose a buffering mode

You can choose between record and table buffering:

  • Record buffering allows pending changes to a single record at a time.
  • Table buffering allows you to have pending changes to several records.
and pessimistic buffering:
  • Pessimistic buffering automatically locks the record as soon as the user starts editing it.
  • Optimistic buffering only locks the record when the user tries to save the changes.

Pessimistic buffering may, at first, look as the better choice, but many programmers prefer optimistic buffering, because it avoids unnecessary network traffic. It also avoids the record being locked for a long time (which may interfere with other users accessing the records). And finally, optimistic buffering is the only option available for a remoteserver (client-server).

Optimistic buffering isn't really unsafe, as it may seem at first sight: if another user applied changes in the meantime, when saving a record with the default options, Visual FoxPro will detect this, and the save will fail. The user may have to apply changes again, but only to the single record that caused the conflict. Besides, this situation shouldn't arise frequently in practice.

Open a table or view

There is nothing mysterious about this. Let's suppose you want to work with a client table; just open the table as usual, with the command:

USE Client

Enable buffering

Before you can enable buffering for any table, you have to give the command

SET MULTILOCKS ON

The technical reason isn't quite clear for me: Visual FoxPro requires this even for record buffering. So let's just accept this as a Visual FoxPro requirement.

Now, let's suppose you decide to use optimistic buffering, which I recommend in most cases. To enable buffering, issue one of the following commands:

CursorSetProp("Buffering", 3, "Client") && Record buffering
CursorSetProp("Buffering", 5, "Client") && Table buffering

The alias can be omitted if it is currently selected.

For pessimistic buffering, the second parameter is 2 (record buffering) or 4 (table buffering). To disable buffering, the parameter is 1.

Do some changes

If buffering is enabled, whenever you issue the REPLACE command, or some other command that changes records in the buffered table, the changes are done only in the buffers - the table on disk remains unchanged, until you decide to save the changes.

Also, now you can set the .ControlSource property of a TextBox - or of any other control that has this property - directly to a field in the table, for instance, .ControlSource = "Client.FirstName". Remember, because of buffering, changes will not be written directly to the table.

Undo changes

To undo changes, just give the command:

TableRevert()

This will undo changes to the current record, in the current table. See the help file for additional options. Save changes

To save changes, you might give the command:

TableUpdate()
This will try to save changes for the current record - and the word "try" should be emphasized. Visual FoxPro might not be able to save for different reasons, the most relevant ones being:
  • A field or record rule is violated.
  • A trigger returns "false" (this includes referential integrity violations!)
  • A uniqueness violation (a key expression already exists in a primary or candidate index).
  • Another user has made changes in the meantime.

Apart from the Visual FoxPro buffers, there can also be buffers at the operating system level, which don't allow the changes to be saved immediately. This can cause trouble (data and index corruption) in the case of a power shortage. Often, the operating system can be forced to commit the changes immediately (and reduce the risk of data corruption), with the command:

FLUSH
To avoid unnecessary disk activity, this should only be done when there are, actually, changes (see below, checking for changes).

Check whether the save was successful

To see whether the record could be saved or not, check the return value of TableUpdate(), for instance:

llCouldSave = TableUpdate()
if llCouldSave
  ...
or:
if TableUpdate()
  ...
If TableUpdate() fails, you can collect error information with aError(). The following example shows the user the error number and the error message (the first two elements of the array created with aerror()):
if not TableUpdate()
  local laErrorInfo(1)
  aError(laErrorInfo)
  MessageBox("Error saving record. Error #" + trans(laErrorInfo(1)) + chr(13) + chr(10);
    + laErrorInfo(2), 16, "Can't save")
You can also provide more sophisticated error handling - or error messages - at least for some common errors, with DO CASE, and the error number.

For an update conflict (another user made changes to the same record), especially error #1585, there isn't much you can do but cancel with TableRevert(). Overwriting changes done by other users is possible, but risky.

For most other errors, the user can correct the input and try to save again. Deleting records

For deleting records, buffering has to be considered too. Specifically, the command:

DELETE
has to be followed by:
TableUpdate()
Again, the return value of TableUpdate() should be checked; the delete might fail, mainly because of referential integrity rules or additional triggers.

New records

When editing existing records, function recno() will return the same value whether buffering is used or not: the position of the record.

On the other hand, if you create a new record, while it isn't saved to disk, Visual FoxPro has to assign a temporary record number, since it doesn't yet know where the record will be saved in the end, or whether it will be saved at all.

What Visual FoxPro does, in this case, is assign negative record numbers for the temporary records.

For instance, if you have table buffering, and three new (not yet saved) records, they will be assigned record numbers -1, -2, and -3.

Finding modified records

When there are several records with pending changes, function GetNextModified() gets the record number of the next record with pending changes.

Checking for changes

Sometimes you need to know whether the current record has pending changes, or what fields were modified. This is done with GetFldState(). Let's suppose that table "Client" has 4 fields. The following example:

? GetFldState(-1)

might return "11121", meaning that the third field is modified (the additional character, at the beginning, is for the deletion status, which might also change).

And the following example:

? GetFldState("ClientCode")

will return 1 (this time, of type numeric) if field ClientCode is unchanged, 2 if it is changed.

For new records, the values returned become 3 and 4, instead of 1 and 2.

It should be noted that, if a user changes a field, and then changes it back to its previous value again, this function will still treat the field as modified. Implicit saving

When buffering is used, TableUpdate() sometimes gets called implicitly. This happens especially when row buffering is used, and another record is selected. If you aren't aware of this, you can get "interesting" results.

To avoid trouble, if you have record buffering, you should make sure that the current record is either saved or discarded explicitly, before the user is allowed to select another record.

Also, before closing the table, you should make sure that pending changes are either saved or discarded explicitly.

Current and old values

If you use buffering, a command such as:

? FirstName

will show you the current value of the specified field, as modified by the current user or data session.

Two additional commands let you access the other values of a field.

? OldVal("FirstName")

will show you the value of the field, before it was modified. This saves you the trouble of saving the original value explicitly.

And the function:

? CurVal("FirstName")

will fetch the value from disk again - in case another user or session changed it.

Transactions

A topic related to buffers is the use of transactions. Briefly, transactions allow you to save changes to several tables, on an "all-or-nothing" basis. Transactions can be used in combination with buffering, but this isn't a requirement.

BEGIN TRANSACTION starts the transaction. After that, you make changes (or save them, with TableUpdate()) to several tables.

END TRANSACTION will commit (confirm) all the changes done since the last BEGIN TRANSACTION, while ROLLBACK will undo all changes done since the last BEGIN TRANSACTION.

All records involved in a transaction are automatically locked until the transaction is ended; therefore, it is recommended to keep the transaction short in time.

Transactions can only be used on tables in a database container - not on free tables.

A simple example for two tables:

BEGIN TRANSACTION
IF TableUpdate(.T., .F., "Table1") and TableUpdate(.T., .F., "Table2")
  END TRANSACTION && Commit changes
ELSE
  ROLLBACK && Undo changes
ENDIF
all records in Table1. Only if this is successful will the program try to save changes to Table2 (with the second TableUpdate()). Only if both TableUpdates are successful, all changes are confirmed, otherwise, the ROLLBACK works like a "global undo".

Version differences

I have tried to keep the syntax simple in the above examples. Therefore, for earlier versions, you should apply the following changes:

For Visual FoxPro 3, you have to put "=" in front of some functions.

trans(...) should be replaced with alltrim(str(...)), if you use Visual FoxPro 3 or 5.

Summary

Buffering allows you to have pending changes to one record, or to several records at a time. It also allows you to postpone the execution of triggers, until the record fulfils all rules. A thorough understanding of buffering is an important step in mastering Visual FoxPro.

Hilmar Zonneveld, Independent Consultant
Hilmar Zonneveld works in programming since 1986, using dBASE, FoxPro and Visual FoxPro. He is available as an independent consultant. He currently works as a programmer at Bata Shoe Organization; also as an instructor at Cisco Networking Academy. You can contact him through the Universal Thread, or, via e-mail, at hilmarz@yahoo.com. Personal Web page (mainly in Spanish): www.geocities.com/hilmarz.
More articles from this author
Hilmar Zonneveld, May 1, 2003
An audit-trail is a record of who did what changes, and when. In Visual FoxPro, this can easily be accomplished through triggers. I hinted at the possibility of doing an audit-trail, in my article on triggers - now, as a reaction to questions in the Universal Thread, I want to present a sample...
Hilmar Zonneveld, December 6, 2001
(The latest update contains minor edits only.) Five easy and fun ways to get yourself into trouble with inheritance. A frequent source of problems in OOP is called "breaking inheritance". This document briefly describes what inheritance is, how it applies to properties and methods, and how it ...
Hilmar Zonneveld, October 6, 2005
Due to a recent Windows security fix, users can no longer access a CHM file on a server. The table of contents appears, but the individual pages are replaced by error messages. Access to CHM files in specific folders can be explicitly allowed through special registry settings.
Hilmar Zonneveld, July 20, 2001
(The last update contains minor edits only.) The idea is to have several controls on a form controlled with an array. Thus, you can quickly go through all the controls on the form, managing the array. The sample code included will help you get started quickly. You can easily adapt it to manage...
Hilmar Zonneveld, September 1, 2002
With Automation, you can control all aspects of Excel, Word, or other programs that provide this feature, from Visual FoxPro. In this article, I will concentrate on Excel. Its purpose is to provide a starting point, especially for people new to automation. Introduction With automation, you bas...
Hilmar Zonneveld, March 1, 2003
Introduction One common task in programming is to keep track of what problems are pending. For this purpose, I use a "hierarchical to-do list": a list of items, each of which can have sub-items. All you need is Microsoft Word. Alternatives are available as freeware or shareware, but in t...
Hilmar Zonneveld, October 7, 2005
This is a step-by-step tutorial to show inheritance, specifically in Visual FoxPro forms, as a guidance for people who are not familiar with inheritance in general, or who don’t know how to implement it in Visual FoxPro. The basic idea of inheritance is that all your forms, or several of your for...
Hilmar Zonneveld, May 30, 2004
The code shows how to quickly obtain the greatest common factor, and the least common multiple. Both functions are used when manipulating fractions, among others. Several methods are possible; the method usually taught in school involves prime numbers, but this code will execute much faster (and it ...
Hilmar Zonneveld, November 1, 2006
A standard requirement in a production system, or in systems for cost calculation, is to add up all the raw materials for a number of finished articles, to get the total cost, or simply to purchase the materials. In this article, Hilmar outlines how to do this with multiple levels of intermediate ar...
Hilmar Zonneveld, August 1, 2002
Overview The purpose of this article is to give an overview of normalization. Basically, normalization refers to having an efficient table structure. I will not discuss the famous "first to fifth normal forms" - if you want that information, enough texts exist about it in other places (search sit...
Hilmar Zonneveld, November 8, 2001
The following function will open any document, with its default association (the same application that will be called when you double-click on the file, in Windows Explorer). Use it to open a text-file, a Word or Excel document, an image, etc., with an external application.
Hilmar Zonneveld, May 1, 2002
Introduction This document explains the meaning of primary key, foreign key and candidate index in Visual FoxPro. A discussion of natural and surrogate keys (keys visible, or not visible, to the end-user) is included, including the advantages of each approach, as well as different methods for o...
Hilmar Zonneveld, January 1, 2003
Continuing my series of introductory articles, this article presents an introduction of a simple yet powerful programming concept: recursion. Introduction "To understand recursion, you must first understand recursion." "To make yogurt, you need milk and yogurt." If you are not accustomed...
Hilmar Zonneveld, December 1, 2002
Introduction This article presents an introduction to coding shortcuts in Visual FoxPro - when to use them, and when not to. Notes on coding in general This article is about coding shortcuts; however, I should first emphasize that making the code as small as possible is usually not the number...
Hilmar Zonneveld, July 20, 2001
Rushmore Optimization can help make queries much faster. However, "Full Rushmore Optimization" is not always a desirable goal. "Partial Optimization" is sometimes much faster. It is often believed that to speed things up, you need to have as many indices as possible. This article explains that so...
Hilmar Zonneveld, June 7, 2002
If you need to check elapsed time with seconds() or a datetime value, this function allows you to display the elapsed time in a human-readable format, that is, hours:minutes:seconds, instead of the total number of seconds. Just pass a number of seconds as a parameter.
Hilmar Zonneveld, April 1, 2002
SQL is a standard language used to manipulate databases. Several of the SQL commands are integrated into the Visual FoxPro language. Select This is a very flexible command, used to select data from a table, or from several tables. This command has options to get totals from several record...
Hilmar Zonneveld, August 1, 2003
In this article, I will show several ways to manipulate text-files. Knowledge of these methods is often important to import and export specific formats. Some of the techniques can also be used to work with files of any content; however, this article will concentrate on text-files. When ...
Hilmar Zonneveld, June 1, 2002
The purpose of this article is to show how to use some aspects provided by the Visual FoxPro database engine, to control our data. Indices Perhaps most readers already know indices; anyway, I find it convenient to include a brief summary of the topic, since this is a requisite to understan...
Hilmar Zonneveld, November 1, 2002
A help file can be used either for interactive help, or as an online manual. In this article, I will give an overview over creating help files in the new help format (CHM), for Visual FoxPro 6 and later. This article is introductory and assumes no prior knowledge of the Help Compiler, or of HTML cod...
Hilmar Zonneveld, February 1, 2003
Introduction Any real-world application will sooner or later misbehave. It is important to be able to find those problems. Visual FoxPro's built-in debugger can help a lot to find out why your program doesn't work as you thought it would. Most of the material in this article applies to Visual...
Hilmar Zonneveld, May 1, 2006
This article is an introduction to VisioModeler. This is a free CASE tool, that can help you design your database, in the process sharing the information with the client in a visual, easy-to-understand, format.