An Introduction To Client
An Introduction To Client
Visual FoxPro
by Scot Becker
Introduction
Note: This article originally appeared in the Virtual FoxPro Users Group October
newsletter and is re-published here with permission of the author and the Virtual
FoxPro Users Group.
In this article, I will explore the many facets of client-server development with Visual
Foxpro. Please note, this article was written under the assumption that not many
significant changes or enhancements were made to the Client-Server structure of
Visual FoxPro version 5.0. Obviously, this article will leave out many complexities
that come with programming on a server. I will try to cover the basics, and leave the
reader to further explore this development technique as his/her situation merits. I will
use Visual FoxPro version 3.x and Microsofts SQL Server version 6.x for all of my
examples. However, most of these examples can be easily transposed to whatever
server platform you have.
Client Architecture:
A few years ago, a 486DX66 with 4 to 8 Megabytes of RAM was a high powered
workstation. Today, this would be considered a low end workstation. However,
those slower machines are still capable of running a well-designed Client-Server
application (with additional memory so Visual FoxPro can run more efficiently). How
often will your company or client upgrade their machines? Relatively speaking, a
Pentium 120 will soon become a Delta 88, but it will still be able to run Client
Server applications. Even if you have a network with excellent throughput, a ClientServer application can still benefit by distributing the load so that you can support
slower (and additional) client workstations. Another advantage is compatibility with
third party products. Not all products, Decision Support Systems in particular, will
support or work efficiently with Visual Foxpro. However, most products will support
a SQL Server architecture.
Performance:
Query speed will be about equal for both Client-Server and Data-Server applications.
However, data entry and processing might fair a little better in a Client-Server
application. For example, a company has a customer support department of over 50
users who are constantly viewing and updating customer records one at a time.
Performance in this case will almost certainly fair better under a SQL server, which is
optimized to handle this kind of overhead.
Data Size:
Another advantage of a data server, a primary advantage for some, is data size.
Currently, Visual Foxpro can only support a Table size of 2 Gigabytes. Any more than
that, and you will have to break your data structure apart. Microsoft SQL Server will
support a table size of up to 8 Terabytes.
Security:
Your data is also far more secure on a data server. DBF file formats are well known,
and nothing (besides encryption tools) is to stop someone from also accessing your
data from another application. On the other hand, nothing can happen on a SQL
Server without a valid user name and password. Furthermore, the database owner has
to grant permissions for users to even see his/her tables and can also limit a user to
certain columns or rows (via a view). Combining SQL Sever security with the
robustness and security features of Windows NT, your sensitive data is quite secure.
Recoverability:
Another advantage of a data server is recoverability. In the event of an unexpected
shutdown, the validity of the data file could become corrupt on a file server. On a
SQL Server, all transactions are logged. At startup, SQL server checks the transaction
log against the data residing in the tables. It will apply any changes that are not
reflected in the tables, or it will rollback any uncommitted transactions from the
tables.
Other Features:
Lastly, SQL Server will support all the referential integrity and other database perks
that Visual FoxPro does (in some cases, better than Visual FoxPro); on-line backup
(you can backup your data while users are on the system), and a fuller SQL syntax
(outer joins; correlated subqueries, inserts, updates, and deletes; and fuller wild card
support). SQL Server is also fully integrated with Windows NT as an NT service.
The quickest way to apply your FoxPro application against remote data is to utilize
Remote Views. A Remote View is just like a Local View; It is nothing more than a
CURSOR that is supplied with data from a SELECT statement. Like Local Views,
Remote Views can either be updatable or read only. The only difference between a
Remote View and a Local View is that a Remote View is supplied with data from a
remote data source.
First, you must set up an Open Database Connectivity (ODBC) connection to your
server via the ODBC administrator. ODBC is basically just a translation layer
between Visual FoxPros version of SQL and your servers version of SQL (the
versions may not always be the same).
You should then setup a connection between Visual FoxPro and your declared ODBC
data source. You do not need to use a connection but you will have to supply login ID
and password each time you open, modify, or requery the remote view. Creating a
connection is easy. From the Data tab, select Connections and then select New.
Select your ODBC data source and add your user ID and password. Supply the
amount of login information that you wish, but remember, if you do not completely
specify the login information, the user will be prompted to enter the missing
information. One problem with the Connection Designer is the fact that it does not
mask your password. This is only an issue if you do not have a secure development
environment and you do not properly distribute your application.
The quickest way (at this point) to create a Remote View would be to use the Remote
View Wizard. You can either base your view on the previously defined ODBC data
source, or on your connection. Again, if you choose the data source, you will always
be prompted for your User ID and Password, but this will not happen if you
completely setup your connection. The rest of the Remote view wizard is selfexplanatory.
Tip: If you want all of the tables fields in your view, you may want to explicitly
choose all of your fields one at a time rather than using the button that selects them
all at once. A fast way to do this would be to first select all fields, de-select one field,
and then re-select it. The reason for this is that Visual FoxPro will implement the
select all button as SELECT * FROM.... rather than SELECT field2, field2,...
FROM..... This will be important if, at a later date, you add field to the remote table
that you do not want reflected in the view.
Next, open your Remote View in the View Designer and modify any of the selection
and filtering criteria (I think you will find that the Wizard is not always adequate in
this area). Then select the Update Criteria tab. First, select a key field (the column
under the key symbol) for your view. Visual FoxPro will not let you write to your
view without specifying a key (also, note that a key can be more than one value).
From there, you can select the fields that you want to update (the column under the
little pencil); If it is all of the views fields, you can select the Update All button.
The Update All button will not automatically select the key to be updatable because,
in some situations, you do not want the key to be modified. If you want your key
fields to be updatable, you will have to explicitly select them. Then select Send SQL
Updates.
Tip: All of these settings can also be specified with the DBSetProp() function.
To set up a buffering scheme, issue a SET MULTILOCKS ON, and set the buffering
scheme with CursorSetProp(). I would recommend scheme 3, Optimistic Row
Buffering, for now, but more on that later. Now open your view, and either BROWSE
it or use a form to modify some of the data. All of your changes are held in the buffer
and have not been sent back to the remote server yet. To update the remote table, you
must issue a TableUpdate() such as:
IF TableUpdate(.T.) <> .T.
=MessageBox(Update Failed, 0, Error)
=TableRevert()
ENDIF
The first parameter in the TableUpdate() function specifies that all updated rows in the
buffer will be sent to the server, and the server will attempt to update them all. If any
of the individual updates fails, due to a trigger, constraint failure, etc., the rows that
are valid will be updated, and those that are not valid will be reverted to their original
values via the TableRevert() function.
The minimum to write a Client-Server application has been completed. However,
Performance will be inadequate. The first step towards optimization is the
parameterized view. This is implemented by a filtering (WHERE) clause except the
expression is evaluated against a memory variable. An example is if you only want to
look at a specific record (or record set), do the following:
SELECT..... WHERE CustomerNo = ?lnCustNo
Before you open the view, assign lnCustNo a value. Note: Visual FoxPro will also
prompt you for the value of lnCustNo if it is not defined from a previous assignment.
Buffering:
Visual Foxpro supports five styles of table buffering all of which are designated with
integer values.
I would recommend option 3, Optimistic Row Buffering (Also note that Pessimistic
locking methods are not supported by Microsoft SQL Server).
CurVal() and OldVal() will return the values of the buffer and actual table data
respectively.
GetFldState() returns the status of a field (appended, modified, deleted, etc.).
GetNextModifed() returns information about modified fields.
CursorGetProp() returns the current property values of the cursor.
AError() returns information about the last error and can be used in building
your error handler.
Refresh() refreshes your view with the modified data.
ReQuery() requeries the remote source.
In addition, there are some tweaks to the Remote View that may prove to be of value.
You can find these under the SQL WHERE Clause Includes section on Update
Criteria tab of the View Designer, and the Advanced Options selection of the
Query menu pad (For Example: how many records to fetch at a time, fetching
memo fields, and connection sharing). The option of particular interest would be
connection sharing. Microsoft SQL Server allocates memory for each connection
(about 4 Megabytes for 100 connections). Connection sharing basically sets all views
to one connection. As long as you are not doing simultaneous fetches, you will never
notice the difference to your application except in increased query execution speed.
Tip: You can use DBGetProp(), DBSetProp(), SQLGetProp(), and SQLSetProp() to
view and alter all of these settings.
Tip: You can also use Asynchronous Processing to prompt users during a potential
runaway query.
Some useful functions that have not been previously mentioned are:
Tip: The SQLTables() and SQLColumns() functions are great for building an
interactive query builder.
Data Partitioning:
To minimize network traffic, you may want to partition your data. In other words,
keep a portion of the data on the local LAN or client machine. Seldom changing data,
such as a table of zip codes or states, are particularly good candidates for this,
especially if you refer to them often. Tables that change infrequently can be stored
locally provided they can be updated automatically or on a scheduled basis. For
example, in a Client Server application that I developed, there was a list that changed
frequently, but not on a regular basis. The solution for this was to make a scheduled
update on a daily basis from the server and to provide a means to temporarily enter a
new list item (until the next days scheduled update) if needed. Data items that
change, but much more infrequently can be updated by a procedure in a menu
command. This will introduce some administrative overhead, but if all your users (or
you) have to do is click a menu option once a month (or whenever the data needs to
be refreshed). This will minimize overall network I/O.
Multiple Users:
An issue that may not always come up (but it will eventually) is how to handle
multiple changes to the same record. For example, two users have the same record
buffered and make changes to the buffer. Then, one user completely updates the
record before the second user. A regular TableUpdate() will return false for the second
user if it sees that the data has changed since the buffer was first written. Which
change is right? There are four approaches, which will usually vary on your particular
application or data structure. First, we can assume that the current user (who may
have the customer on the phone) is always right. You would then issue a forced table
update by calling TableUpdate(.T. ,.T.). The second strategy would be to make the
user requery the data for comparison before changing it. In this case, a
TableUpdate(.T.) will return false, and you will have to issue a TableRevert(). Then, if
you have a good error handler, you can prompt the user to try the change again (after
they see the other users changes). The third approach would be to issue a
TableUpdate(.T.) and update all the records you can, and then notify the user which
update failed by utilizing the GetNextModified() function, then issue the
TableRevert() function. The last option is to let the user decide which change to
implement by using the GetNextModified() function, presenting the user with both
options, and letting him/her chose. Then, issue the forced update (TableUpdate(.T.
,.T.)) or TableRevert() as needed.
Data Validation:
Generally, you want to keep all data validation rules on the server. This minimizes the
burden of version control. To effectively develop this strategy, a robust error handler
can trap, report, handle, and interpret ODBC errors. On the other hand, if you apply
data partitioning, you will have to implement some sort of local data validation as
well, whether it is via code or local table level validation.
Referential Integrity:
Referential Integrity should always be handled by the server (except for local table
referential integrity, if any). SQL Server makes this task easy by using of constraints,
keys, triggers, and rules; Again, you will need a robust error handler.
Security:
Data security should always be handled by the server. User rights can be as specific or
as general as you like through the use of stored procedures, permissions, and views. If
you want to secure any local data, you will need to account for this in your
application, but all sensitive data should always be kept on the server.
Server Administration:
There are many server level issues to consider. These issues are neither a trivial nor
always quick to solve. Backup strategies; number of users; memory; storage space;
writing of triggers, rules, indexes, and constraints; keys; user and group permissions;
and other administrative tasks need to be considered. Also, someone is going to have
to oversee the daily maintenance of the server. You will need some good books and/or
classes, time, and patience, or find someone who is capable of those administrative
tasks.
Conclusion
Visual FoxPros Client-Server capabilities are, in my opinion, second to none. By
utilizing the concepts discussed in this article, you can provide added value for your
company or client. These skills will always be useful, and should be included in the
toolbox of any serious developer. Remember, you do not need a SQL Server to
practice these skills or utilize any of the techniques that I have discussed here. Any
ODBC source, even Visual FoxPro databases, can act as a remote data source. Also,
even if you do not plan on writing any Client-Server applications, you can still use
many of the techniques discussed in this article in any multiple user environment.
The Author would like to thank (in alphabetical order) Mike Bisek, Chad Coon, and
Leah Quam for their assistance in formulating this document.
This document is Copyright, 1996 by Scot Becker and may not be re-printed
or re-published in any format without the permission of the author and the
Virtual FoxPro Users Group
This page was last modifed: undefined (this is not always available on all browsers)
[email protected]