Welcome to Foxite.COM Community Weblog Sign in | Join | Help

DataSessions In Visual FoxPro

It has been a while since I wrote something specifically related to VFP, so here goes. Recently I have seen a spate of questions, in various forums, relating to data sessions and their purpose and use. It is clear that although the concept has been around in VFP since Version 3.0 (released in 1995!) they are still not really very well understood. In the hope of clarifying things I'll try to pull together the threads that link working with datasessions together.

Introduction

Let's start with the help file definition of a datasession:

A data session is a representation of the current dynamic work environment. You might think of a data session as a miniature data environment running inside one open Visual FoxPro session on one machine. Each data session contains:

      A copy of the items in the form's data environment.
      Cursors representing the open tables, their indexes, and relationships.

Not terrifically helpful as it stands, except that it does make one very important statement – that a datasession runs inside one VFP session on one machine. That is the first thing to remember about datasessions – they are local to the workstation on which they are created and therefore they provide a mechanism for implementing "data isolation". Neither another user on the network nor even another instance of Visual FoxPro on the same workstation can directly access any datasession but their own.

What does that mean in practice? It means that the effect of any changes that are made to a data source (i.e. a Table, Cursor or View) in a given datasession – whether that is simply moving the record pointer to a different record, or changing the content of a record – is restricted to that datasession. Since each datasession is self contained, data changes made inside one are only visible to other datasessions when those changes are saved to disk and the other datasession refreshes its own version of that data. In this respect at least, datasessions mimic the behavior of forms in a multi-user environment by isolating the data that they contain from changes made elsewhere in the environment.

The Datasession Property

Contrary to what you may believe, there is only one type of datasession in Visual FoxPro and that is the PRIVATE datasession. In reality there is no such thing as "The Default" datasession because all datasessions are always created as private and all private datasessions are created equal. No one datasession has any special significance or meaning and the confusion arises because of the use of the word "default" to describe Private DataSession #1.

When you start Visual FoxPro, it needs a datasession to work in, so VFP creates one; what it actually creates is "Private Datasession Number 1" and it creates it by default.  However, objects that can create datasessions (i.e. Forms, Reports, Toolbars, Sessions and Formsets) use a property named "Datasession" to determine how to behave. This has two possible values that are defined as:

1 = Default Data session (Default for everything except Session Objects)

2 = Private Data session. Creates a new data session for each instance that is created (Default for Session Objects)

and this is where the confusion arises!

It seems obvious that setting an object's datasession property to "1 - Default Data Session" will ensure that the object will always use the datasession that VFP refers to as "Default" (i.e. Datasession Number 1!). However, this is not correct!

In this particular context the term "Default" means only that the object does NOT create a private datasession for itself and, instead, uses whatever datasession is current when it is instantiated. The consequence is that when an object whose datasession property is set to "1 -Default Data Session" is created by another object that has its own private datasession (i.e. a datasession other than #1) it does not create its own new datasession and merely opens up in the same datasession that the calling object is using.

So, if you create a form that always creates a private datasession (Datasession = 2) and, from that form call another form whose Datasession Property = 1 (Default Data Session), then the child form will open up in the same (Private) datasession that the calling form is already using. However, if you were to call the same form when no other object exists (e.g. from the command window) it would open up in the Default Datasession (i.e. #1 ) because that is the only datasession available and so it uses it "by default".

If you think about it, that actually makes perfect sense and is entirely reasonable. What makes it confusing is the use of the word "default" to mean either "DataSession #1" or "Whatever Datasession is current". In practice, therefore, the interpretation of an object's Datasession Property should always be:

      1 = Use whatever Data Session is currently active

      2 = Create a New Private Data Session

The DataSessionID Property

Now that we understand the implications of the Datasession property, let's take a look at the DataSessionID. This property identifies the datasession number being used by an object. However, it is not merely a means of determining which datasession an object is using, it can be set directly to make an object change the datasession which it is using. The following little program illustrates how this works:

CLEAR
*** We will create these as Public Objects so that they will persist
*** after the program finishes and we can manipulate them in the Command Window
RELEASE goSess2, goSess3
PUBLIC goSess2, goSess3
*** Open the Customer Table in the Default Datasession (i.e. DS #1)
USE (HOME()+ 'Samples\Tastrade\Data\customer')
GO TOP IN customer
*** Now let's create a Session Object which will get DS #2
goSess2 = NEWOBJECT( 'Session' )
*** Change to that datasession
SET DATASESSION TO 2
*** And open the same table here
USE (HOME()+ 'Samples\Tastrade\Data\customer')
*** But go to a different record
GOTO 25
*** And repeat the process for another private DS = #3
goSess3 = NEWOBJECT( 'Session' )
SET DATASESSION TO 3
USE (HOME()+ 'Samples\Tastrade\Data\customer')
GOTO 50
*** Now see what we have - get the Datasessions into an array
? 'Starting Conditions: '
lnSess = ASESSIONS( laSess )
FOR lnCnt = 1 TO lnSess
  lnDS = laSess[ lnCnt ]
  SET DATASESSION TO (lnDs)
  ? 'DS #' + TRANSFORM(lnDs) + ': Using ' + DBF() + ' at Record Number ' + TRANSFORM( RECNO())
NEXT

As you can see if you run this program, we end up with three datasessions, each with an instance of the same table, but each with the record pointer positioned on a different record. We have achieved data isolation! If you now look at the Data Session Window and you will see that there are in fact three datasession (numbered 1 through 3) current.

 

Figure 1: Multiple Concurrent Datasessions

Notice also that now that these sessions exist, they persist as long as the object that created them exists, even if that object is no longer using the session it created. You can see this by executing the programs above, then running the following commands from the command window and examining the Data Session window:

goSess3.DataSessionID = 1 && Set Object 3 to use Datasession #1

Datasession 3 is still there – even though it is no longer used by the object that created it. You can even close the table in that datasession by issuing:

SET DATASESSION TO 3

USE

and the session remains. However as soon as you release the owning object, the datasession vanishes:

RELEASE goSess3      && Now Session #3 goes away

Notice that you are now back in DataSession #1 (the 'default'). This is because Datasession #3 no longer exists, and at the moment it was released we were in it. Obviously VFP has to go somewhere, so it goes to Datasession #1 since that datasession will always be available because it is created by VFP itself and cannot be released. So in this sense DataSession #1 actually is the default datasession - it is the one chosen when nothing else is specified.

Now release our second session object.

RELEASE goSess2      && Now Session #2 goes away

Notice that the session was closed even though there was still an open table in that session. In this scenario, any pending changes in tables in this datasession get reverted automatically – a fact that should be borne in mind!

All objects that can create datasessions behave in the same way and you can, in practice change the datasession being used by a form simply by setting its DatasessionID just as we did for our session object. Of course, if you have controls on a form that are bound to a table that only exists in the form's datasession you would be in trouble if you tried to do something that required the form to access that table – which is why, in the help file entry on SET DATASESSION there is a cautionary note that:

Care should be taken when issuing this command when a form is active, because tables in non-current datasessions are not accessible

However, as long as you do not execute any command that would cause a form to re-query a table there is no real restriction on changing it's datasession. One case where you may want to do so is to retrieve a record from a table that is open in another datasession (like a Search Form maybe) which you can do very simply using the SCATTER NAME command like this:

*** Save current Datasession
lnSaveSession = ThisForm.DataSessionID
*** Change into to the required session owned by another object
ThisForm.DataSessionID = SomeOtherObject.DataSessionID
*** Grab the data you need (Select table and find record)
SCATTER NAME loData
*** Return to the correct datasession
ThisForm.DataSessionID = (lnSaveSession)
*** And do whatever you need to with the Data object
ThisForm.SomeProperty = loData.ColumnName

Datasessions and the Environment

There are some important points to note about using datasessions because several settings in Visual FoxPro are scoped to the datasession. This means that, whenever you create a new private datasession, these settings are set to their Visual FoxPro default values in that datasession, irrespective of whether you have specified some other value in your startup program. In other words, values in your startup program apply only to datasession #1 and you must handle all other datasessions explicitly when they are created. The commands that are scoped to the datasession are:

Table 1: Settings Scoped to the DataSession

Notice that there are some very significant ones here:

·         SET EXCLUSIVE is ON by default in DataSession #1 (referred to very poorly in the help topic as the "global data session" – it isn't "global" in any sense of the word as we have already seen) and OFF by default in private datasessions

·         SET DELETED is OFF by default in all sessions which means that records marked for deletion are always visible, and are always included in commands that operate on records

·         SET TALK is ON by default in all datasessions (interestingly the closely related commands SET ECHO and SET NOTIFY are not scoped to the datasession!)

·         SET CENTURY is OFF by default, and SET DATE is "American" in all datasessions

·         SET MULTILOCKS is OFF by default in all sessions. This one is really a dumb default since any attempt to use buffering requires that MultiLocks is ON. So unless you set it each time you create a private datasession you will get an error if you try to implement buffering. Since views are ALWAYS opened buffered this means that you cannot use a view in a private datasession unless you first enable MULTILOCKS

·         SET DATABASE defaults to none in all sessions. This means that if you are have multiple databases and have a specific one set "Current" (so that commands that rely on a database being set will work) that setting is lost when a new datasession is created

The best solution that we have found to these issues is to define, as part of our root form class the specific settings that we use as defaults. These are defined in the form's LOAD() event so that they are set before any other form event is fired.

Note: If you use the form's dataenvironment  to open tables for you, you will need to handle settings like Exclusive in the DE's BeforeOpenTables because the form's dataenvironment is created before the form Load() event.

Here are the ones that we use:

*** Basic Locking and Environment settings
SET TALK OFF
SET MULTILOCKS ON
SET REPROCESS TO AUTOMATIC
SET DELETED ON
SET SAFETY OFF
SET BELL OFF
SET ECHO OFF
SET NOTIFY OFF
SET CONFIRM OFF
SET EXACT OFF
SET CENTURY ON
SET REFRESH TO 60,60

There are, of course, many other ways of handling this. Many people use a custom "Settings" class that can be dropped on to a form at design time, or a globally available procedure, to handle these (and other) default settings. There is no 'right' answer, but this way is simple enough and works for us because we normally instantiate our forms as classes anyway, so don't have any DE related issues.

Conclusion

You may be wondering, about now, why on earth you should bother with all this. After all if just stick to using Datasession #1 for all our forms the settings that we define at startup will apply. If your application uses only forms that are modal, and so can only ever have one instance of any form at a time, then this will work for you. While it is not very flexible, or user friendly, it certainly makes your life as the developer much simpler.

However, if you want to create a more flexible interface where users can open multiple instances of forms, or reports, simultaneously, then you will definitely need to get to grips with Private Datasessions. In fact, even if you do not need to handle multiple instances of the same object, if you ever have anything other than a single modal form active at any time you should be using private datasessions. This is because even though you may not allow multiple instances of the same form, it is entirely possible that different forms will use the same table. If you do not use private datasessions, then changes to the data in one form will affect all others – which may not be exactly what you want!

 

Published Sunday, December 21, 2008 1:48 PM by andykr
Filed Under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: DataSessions In Visual FoxPro

Tuesday, December 23, 2008 8:55 PM by Dantv

Thank you for this article. Very helpful information!!! The fox community is superb!

Glad you found it useful and thank you for reeading and your comment.

# re: DataSessions In Visual FoxPro

Wednesday, December 24, 2008 1:24 PM by Ron Philippo

very informative article Andy, thank u

i have one question for u regarding this subject:
whats the best way or not, to have a private datasession for a container object, which u can drop on every form?

I would use a session object which is loaded in the Init() of the container, that will create a Private Datasession. Then you can use code to check for the presence of a form and, if one is found, set its Datasession to that of the Session Object. Like This:

WITH This
*** Add a property for the Session Object
*** (even better do it in the class! This is just on a form)
  .AddProperty( 'oSesObj', NULL )
  .oSesObj =
NEWOBJECT( 'Session' )
 
IF TYPE( .Parent.Name )='C'
   
IF UPPER( .Parent.BaseClass ) == 'FORM'
     
.Parent.DataSessionId = .oSesObj.DataSessionID
    ENDIF
  ENDIF
ENDWITH



 

# re: DataSessions In Visual FoxPro

Thursday, December 25, 2008 4:20 PM by Ron Philippo
IF TYPE( ".Parent.Name" )='C'

Inetresting, thanks

# re: DataSessions In Visual FoxPro

Friday, December 26, 2008 3:03 AM by Eric J. Muñoz

Again, you just saved me from another headache, like... right now! =D

Greetings from Guadalajara, México and happy holidays!

Merry Christmas, and a Happy New Year to you also

# re: DataSessions In Visual FoxPro

Sunday, December 28, 2008 9:19 PM by Steven Black
Excellent article, Andy!

Though your article makes this point implicit, I'd like to expand on it if I may.

One of my favorite VFP techniques is to use a table-driven class factory to create my objects.

If the class factory is a subclass of the VFP Session class, then all objects created by the class factory will have and hold the factory's datasession.

So need a custom object with it's own private Idaho?  Then
1- Create a class factory, a subclass of Session.
2- Use the class factory to create your custom object.
3- Release the factory at will.  The custom object's reference keeps the data session alive even though the factory is gone.  Later, when the custom object dies, then so does the datasession.

This is one of the sweetest tricks in the realm.

Cheers!

VERY Nice. Thanks for sharing that one Steven and for those who are not familiar with the factory concept there is an article on this topic on my blog - see "Creating Classes With a Factory" here: http://weblogs.foxite.com/andykramek/archive/2007/08/04/4508.aspx

What do you think?

(required) 
required 
(required)