Originally published in FoxTALK April 1999
Buffering, The Vampire Slayer
In FoxPro 2.x we spent a lot of effort to preserve record values so we could offer the user an opportunity to revert their edits. Visual FoxPro gives us a mechanism to make this issue easier to handle. Data buffering is the tool to use for this. In this article and the next few we will examine the nuances of data buffering and the other ancillary requirements in Visual FoxPro. This month, the basics of data buffering.
To buffer or not to buffer
The first question is "Should I use data buffering at all, or should I just keep on going with SCATTER and GATHER to handle the problem?" Well the answer is not simple. SCATTER and GATHER require that the variables or array be scoped properly so that they are visible to all of the objects that need to access them. Since variables are, by default, privately scoped (that is they are destroyed when the routine that created them terminates) we have a problem.
If in a form method I SCATTER MEMVAR those variables go out of scope as soon as the formís method ends, unless I declare all of the variables as PUBLIC before doing the SCATTER. Declaring variables public has its own set of problems that are beyond the scope of this article, so letís just agree that we donít want to declare variables public unless there is absolutely no other way to do what we want to do.
With VFPís controls and their ability to be bound to data, it makes sense to have the data available during the creation of the control. The creation sequence is such that the Data Environment (DE) of a form is created before any of the controls are created. This allows the DE to open the data cursors before the controls try to bind to their controlsources. If we bind the controls directly to the fields in the cursors this all works well, however, if we are binding to memory variables we must insure that those variables exist at the time the controls are created.
This variable creation could be done in the formís Load event (which fires before the controls are created) by declaring the variables as public and then scattering to them. Oops, there goes that public stuff again (where is a vampire slayer when you need one).
Data buffering gives us the best of both worlds, we can bind directly to fields and we can control the updating or reverting of the record on disk. By binding directly to fields the whole variable scoping problem disappears.
Is there ever a situation where you might not want any buffering? Yes, if part of the requirements is that edits are committed immediately and irrevocably. In this situation no buffering is the way to go. Also, if you have a table that is read only, then you can set buffering to none.
What is data buffering?
In FoxPro 2.x, when we issued our GETs against fields in a table we said we were doing direct edits. We called them direct because we assumed that the fields in the table were being directly edited. We had no control over the updating process. In fact, FoxPro 2.x was doing the edit in a memory buffer of the record, which would later be used to update the table. In FoxPro 2.x we had no control over the buffer, it would be written when FoxPro got around to it and there was no way we could stop it from happening.
In Visual FoxPro we have data buffering which gives us control over that edit buffer. It allows us, when buffering is on, to control when and if the buffer gets committed to disk. Data buffering is simply a technology that makes it easier for us to do what we were doing before.
Visual FoxProís data buffering adds some functionality to the process. With the use of functions like OldVal() and CurVal() we can find out what the buffer started with for values and find out what is currently on disk respectively. We will see a number of other functions that enhance the data buffering in VFP as we go along on this exploration.
Why 5 or 6 buffering modes?
There are actually 5 buffer modes, but in the BufferModeOverride property for a cursor in a DE you will see 6 choices. The additional choice is "Use the form setting", which causes VFP to use the BufferMode setting for the form and to decide on table or row based on the type of control being used (more on this later).
The 5 buffer modes are;
With optimistic buffering, VFP does not lock any records when editing begins. Instead, when an attempt to update the table occurs, VFP checks to see if the record it is updating is the same as what our buffer started with. If they are the same the update is done, if not then the update is not done (more on this later).
With pessimistic buffering VFP attempts to lock the record when an edit begins, if it can get the lock the edit is allowed. If VFP cannot get the lock then an error condition (ďRecord is in use by anotherĒ) occurs and the edit is rejected.
Row buffering allows only one record to be dirty in the buffer at a time. Dirty, as used here, means that the record has been edited in the buffer. An attempt to move the record pointer in a cursor that has row buffering on will cause VFP to implicitly attempt to update the table.
Table buffering allows multiple records to be dirty in a cursorís buffer. Moving the record pointer does not have any implicit updating activity.
By combining the Optimistic/Pessimistic with the Row/Table you get 4 separate buffer modes in addition to the None setting.
Which one has the right stuff?
First letís look at Optimistic versus Pessimistic. Pessimistic locks records on the server. Pessimistic insures the right to write before it allows the edit to begin. These seem like some very valid reasons to prefer pessimistic buffering. Well, before we draw any conclusions letís look at the downside of pessimistic buffering.
Pessimistic buffering locks the record when the edit begins and holds that lock until the update occurs. That means if Mary starts an edit and then goes to lunch, no one can work on the record that Mary is editing. Pessimistic uses locks at the network server which consumes server resources. Often the network server has a limited number of simultaneous locks available and if your number of users grows you could end up with network errors when the pessimistic buffering tries to get a lock and canít.
Optimistic buffering does not secure locks until the update occurs. It gets the lock, does the update, and release the lock. These locks are held briefly. But optimistic buffering may fail to update because of an update conflict while pessimistic wonít. Isnít that enough reason to prefer pessimistic? No, using optimistic buffering one can secure their own lock when and edit begins and release the lock when they want to, so optimistic buffering can provide the same functionality as pessimistic. The problem as I see it, is that pessimistic buffering gives me no choice or control over what to lock and when (it happens by magic) while optimistic gives me that control. I hate magic! So, my preference is optimistic buffering for everything. If I need to insure the writing of data I will handle that in my code.
How about row versus table? The same reasoning applied to optimistic versus pessimistic can be used here. Row buffering allows only one record to be dirty at a time and it does a magic update if the record pointer moves. Table buffering does no magic stuff at all, because it allows multiple records to be dirty at once it does not need to update when the pointer moves. You can restrict the editing to only one record at a time through the user interface, donít let them move to another record while an edit is in progress.
Why is the implicit update a problem? VFP is object oriented, as such we create classes that have specific behavior. We try to make these classes generic so they can be used in multiple places. If you create a form, use row buffering, and then at a later time add another control to the form based on a class that looks something up in a cursor, you very well may introduce an unexpected automatic update of the buffer. Not only can this situation be created, it would be a bear to try and debug. With table buffering, there is no magic going on and therefore the issue here becomes a non-issue.
So, then, if I read the above correctly the conclusion is to use Optimistic Table buffering all the time. Yup, thatís how I do it. If I need single record editing I code it. If I need insured writes I code it.
How and where do I set the buffer mode?
You have a variety of ways to set buffer modes. They are the formís BufferMode property, the cursorís BufferModeOverride property, and the CursorSetProp() function.
Formís BufferMode property
The BufferMode property of a form gives you three choice for setting the buffer mode. They are 0-None, 1-Pessimistic, and 2-Optimistic respectively. The row or table option of the buffer mode is handled by the control that the table is bound to, if bound to a grid table buffering is used and if bound to another control row buffering is used.
Cursorís BufferModeOverride property
The BufferModeOverride property for a cursor in a DE allows 6 settings. The options are 0-None, 1-Use form setting, 2-Pessimistic Row, 3-Optimistic Row, 4-Pessimistic Table, and 5-Optimistic Table. The BufferModeOverride should be set for every cursor in the DE if you want a setting other than 1-Use form setting (the default).
The CursorSetProp() function can be used to programmatically set the buffer mode. The CursorSetProp() function has the following syntax;
CURSORSETPROP(cProperty [, eExpression] [, cTableAlias | nWorkArea])
To set buffering modes the first argument is ďBUFFERINGĒ, the second is the number of the buffer mode desired as listed in the BufferModeOverride section above, and the last is the alias name of the cursor in which you want to set the buffer mode. For example, if I wanted to set optimistic table buffering in a cursor with the alias of Customer I would;
CursorSetProp(ďbufferingĒ, 5, ĒCustomerĒ)
In order to use CursorSetProp() to set a buffering mode the SET MULITLOCKS option must be ON (it is off by default unless you have changed this in the Tools-Options dialog). Setting Buffer modes in a form does require explicit setting of the multilocks option as the form will automatically set the multilocks for you.
How do you control the updates?
The updating process is controlled through two functions, TableUpdate() and TableRevert(). The former does an update of the record and the latter undoes the edits and reverts the buffer back to what is currently in the record. Next month we will investigate these two functions in more depth.
"We can do this the hard way or we can do this the easy way." Data buffering is the easy way, once you understand how it works. It takes less code to accomplish the same results as the FoxPro 2.x methodology and it allows for the exploitation of the data binding capabilities of the VFP controls. There are a lot of choices related to data buffering. A careful examination of each of these choices will go along way in simplifying making the decisions.