Moving to the New Data-Related Classes in Visual dBASE 32

François Ghoche, ATON Consulting + V.I.P. Group (France)


Warning : This paper has been written while the product build available was not final. Some innacuracies may be found. Also, the tables of properties have been inspired by the properties of the equivalent classes in IntraBuilder.The list of properties in the final shipping build of the Visual dBASE 32-bit version should be a superset of these properties. The reader will please not consider this paper as an accurate and complete description of the final version of the product.


 


The new name of the game: data encapsulation and SQL


In Visual dBASE 5.x, when linking UIControl objects to data tables, the dataset declaration is best accomplished using the View property of the Form class.

If needed in this case, a query can also be used as a view. The query QBE file being composed of standard dBASE DML (Data Manipulation Language), it can also include statements to declare and open a database alias.

However, the view/query does not isolate the dataset from possible interference from other objects. Two forms using common files in their View properties may corrupt each other’s views, when sharing the same tables. Of course, a means is provided to deal with such a situation and handle a better encapsulation of data: the CREATE SESSION command.

The drawback of the session as simplemented in version 5.x is well known. It has a life but does not have a name. The developer must rely on a good knowledge of the behaviour of sessions in order to control them.

I have previously explained the various aspects of using these features for dataset encapsulation in my talk at BDC’96. These are also discussed in my paper Encapsulating Datasets and Transactions in Visual dBASE (www.fghoche.com/db120.htm).

Handling the various side-effects of mixing object-oriented programming together with the traditional dBASE DML has taken a lot of trial and error learning and workaround build-up, that most developers are familiar with.

The 32-bit version of dBASE comes with a set of new data-related classes. The objects built from these classes will enable the developer to extend the advantages of object encapsulation and class inheritance to datasets.

The new classes do not use the traditional dBASE DML. The SQL language is used instead to build the queries and work on the resulting data view.

SQL is the established standard for a Data Manipulation Language (DML). It is also the DML used in the leading relational database engines on the market.

The Borland Database Engine (BDE) makes it possible to use SQL for native tables (DBF and DB) as well. The handling of SQL for native format tables has been enhanced in the new version.

Note that Xbase DML may be used to manipulate objects in some instances.


The old and the new way


It is important to understand how the new features relate to the traditional dBASE language.

Here is some sample code from a standard Visual dBASE query in a QBE file, together with a diagram showing equivalent data-related components usage (no direct relationship with the diagram):
* Fichier .QBE Visual dBASE 9
CLOSE DATABASES
SET EXACT ON
SELECT 1
USE POLACQUI.DBF
SELECT 2
USE FOURNIS.DBF ORDER TAG  NFOURN
SELECT 4
USE RECEPT.DBF ORDER TAG  NUM_COMMAN
SELECT 1
SET RELATION TO NFOURN INTO FOURNIS,
    NO_COMMA INTO RECEPT CONSTRAIN
SET SKIP TO RECEPT
SET FILTER TO ISBLANK(Polacqui->
    Date_Comma).AND.
    ISBLANK(Polacqui->No_Comma)
SELECT 3
USE FOURINIT.DBF
SET SKIP TO RECEPT
SELECT 5
USE DICTION.DBF ORDER TAG TABLE
SET SKIP TO RECEPT
SELECT 1
GO TOP

In the new paradigm, this DML is replaced by a number of objects. The diagram summarises the most simple situation, where a Query and its dependent properties only are needed, which we will study first.


Query class & family vs. Use, Set relation & dBASE DML

Query class

The first and most important of the new classes is the Query class. It hosts an SQL query and it will be used to establish the link between the data in the tables and the controls and methods in the form object.

The easiest way to put a Query object on a form is to use the drag and drop feature from the Navigator. You just highlight the Query or table file name in the Navigator, click and hold down the mouse’s left button, drag to the form (or report) layout in the Designer, and then drop it. The basic properties relating the object to the table(s) will be then automatically filled-in by the designer.

Using this method, you will be able to inspect the query object in the Form designer, to see what properties have been set. Of course, you can also drag a Query component from the component palette onto the form and inspect it to set the various properties by yourself.

The first task of the Query object is to act as a container for an SQL SELECT statement. The statement describes the query. When executed, the resulting view is accessed through the Rowset object, which we will study next.

The query statement is executed when its Active property is set tot True. If it succeeds, a rowset is then available. Otherwise, the Active property is set back to False.

If the resulting data cursor is empty, the EndOfSet property is set to True.

Example

q= new Query()

q.sql = "select * from VACATION"

q.active = true

Syntax

[<oRef> =] new Query()

<oRef> A variable or property—typically of a Form or Report object—in which to store a reference to the newly created Query object.

Properties

Property Default Description
active false Whether the query is open and active or closed
className Query Identifies the object as an instance of the Query class
constrained false Whether WHERE clause of SQL SELECT statement will be enforced when attempting to update Standard tables
database null Database to which query is assigned
handle   BDE statement handle
parent null Container form or report
requestLive true Whether want writeable rowset
rowset null Results of query
session null Session to which query is assigned
sql Empty string SQL statement that describes query
unidirectional false Whether to assume forward-only navigation to increase performance on SQL-based servers
updateWhere AllFields Enum to determine which fields to use in constructing the WHERE clause of a SQL UPDATE statement, used for posting changes to SQL-based servers
Event Parameters Description
canClose   When attempting to close query; return value allows or disallows closure
canOpen   When attempting to open query; return value allows or disallows opening
onClose   After query is closed
onOpen   After query first opens
Method Parameters Description
prepare( )   Prepares SQL statement
requery( )   Rebind and execute SQL statement

  

When you activate a Query object, some more objects are automatically activated as well, as properties of the Query.

Rowset class

The first one is a rowset. The rowset object is automatically instanciated as a property of the Query. It is somehow related to the concept of the Record, which is well known to dBASE developers. The difference is that a record contains all the fields' values of one row for a given table, while the rowset contains the active fields of the rows of all the related tables in the view.

This means that it does not contain possibly filtered fields, for instance. It also means that the rowset's row behaves in a similar way to the table's record. For example, there is only one row pointer (cursor) per rowset, in the same way as there is only one record pointer per table in a given work area.

If the query is successful, the Field property of the Rowset will point to an array object, which elements represent the fields in row of the tables. We shall get to the fields later on.

The rowset is equivalent to opening a table in a workarea, with SET FIELDS, SET FILTER, etc. Rowset's fields array represents the current record's data.

Changing the data

As expected, a buffer is associated to the rowset. Methods are available to handle the data in the buffer and save or abandon the modified data.

This may be different from the curent behavior in Visual dBASE 5.5, where the table record buffer is always in Edit mode by default. Only form.BeginAppend() was necessary, in order to switch to append mode.

In Visual dBASE 32, the AutoEdit property of the Rowset object determines whether the rowset fields are changeable. If AutoEdit is False, the BeginEdit() method enables to switch to Edit mode. BeginAppend() activates the append mode.

Filtering and locating

An automatic filtering and locating feature is available for implementation in forms, providing for an easy filter/locate-by-form feature. This is a two-step process in this case.

  1. Prepare the filter / condition
  2. Execute the task

The BeginFilter() and BeginLocate() methods put the rowset in the appropriate state. One interesting result is that the entryfields are cleared. The data keyed in by the user will be used to build the filter (or locate condition).

When the method ApplyFilter() / ApplyLocate() is called, the data is automatically used to perform the task.

Of course, it is also possible for the developer to use the Filter property to set the filter/locate condition. Setting the filter property applies it immediately and BeginFilter() is not necessary in this case.

Use the ClearFilter() method to remove filter condition.

The following tables list the properties, events, and methods of the Rowset class.

Property Default Description
autoEdit true Whether the rowset defaults to edit mode or requires beginEdit( ) to be called
className Rowset Identifies the object as an instance of the Rowset class
endOfSet   Whether the row cursor is at either end of the set
fields   Array of field objects in row
filter Empty string Filter SQL expression
filterOptions Match length and case Enum designating how the filter expression should be applied
handle   BDE cursor handle
indexName Empty string Active index tag
live true Whether the data can be modified
locateOptions Match length and case Enum designating how the locate expression should be applied
masterFields Empty string Field list for master-detail link
masterRowset null Reference to master Rowset object
modified false Whether the row has changed
notifyControls true Whether to automatically update dataLinked controls
parent null Query object that contains the Rowset object
state Closed Enum that describes mode rowset is in
Event Parameters Description
canAbandon   When abandon( ) called; return value allows or disallows abandoning of row
canAppend   When beginAppend( ) called; return value allows or disallows start of append
canDelete   When delete( ) called; return value allows or disallows deletion
canEdit   When beginEdit( ) is called; return value allows or disallows switch to edit mode
canGetRow   When attempting to read row; return value acts as an additional filter
canNavigate   When attempting row navigation; return value allows or disallows navigation
canSave   When save( ) called; return value allows or disallows saving of row
onAbandon   After successful abandon( )
onAppend   After successful beginAppend( )
onDelete   After successful delete( )
onEdit   After successful beginEdit( )
onNavigate   After rowset navigation
onSave   After successful save( )
Method Parameters Description
abandon( )   Abandons pending changes to current row
applyFilter( )   Applies filter set during rowset’s Filter mode
applyLocate( ) [<locate expC>] Finds first row which matches specified criteria
beginAppend( )   Starts append of new row
beginEdit( )   Puts rowset in edit mode, allowing changes to fields
beginFilter( )   Puts rowset in Filter mode, allowing entry of filter criteria
beginLocate( )   Puts rowset in Locate mode, allowing entry of search criteria
bookmark( )   Returns bookmark for current row
clearFilter( )   Disables filter created by applyFilter( ) and clears filter property
count( )   Returns number of rows in rowset, honoring filters
delete( )   Delete current row
first( )   Moves row cursor to first row in set
goto( ) <bookmark> Moves row cursor to specified row
last( )   Moves row cursor to last row in set
locateNext( ) [<rows expN>] Find other rows which match search criteria
lockRow( )   Locks current row
lockSet( )   Locks entire set
next( ) [<rows expN>] Navigates to adjacent rows
refresh( )   Refreshes entire rowset
refreshControls()   Refreshes dataLinked controls
refreshRow( )   Refreshes current row only
save( )   Saves current row
unlock( )   Releases locks set by lockRow( ) and lockSet( )

Field class

One more step down in the class hierarchy after the rowset, there are some Field objects.

When the Query is activated, the Fields property of the Rowset object points to an Array object with one element per field in the rowset. Each element of the array contains a Field object

As previously explained, the Fields objects will enable datalinking to the tables' fields on disk.

The Field class acts as the base class for the DbfField (DBF table), PdxField (DB table), and SqlField (everything else) classes. It contains the properties common to all field types. Each subclass contains the properties specific to that table physical format.  

Properties

Property Default Description
className Field Identifies the object as an instance of the Field class
fieldName   Name of the field the Field object represents
length   Maximum length
parent null fields array that contains object
type Character The field’s data type
value Empty string Represents current value of field in row buffer
Event Parameters Description
beforeGetValue   When value property is to be read; return value is used as value
canChange <new value> When attempting to change value property; return value allows or disallow change
onChange   After value property is successfully changed
onGotValue   After value is read
Method Parameters Description
copyToFile( ) <filename expC> Copies data from blob field to external file
replaceFromFile() <filename expC>
[, <append expL>]
Copies data from external file to blob field

 Here are now the sub-classes related to the Field class:

DbfField class

Properties

The following tables list the properties, events, and methods of the DbfField class. DbfField objects also contain those inherited from the Field class.

 

Property Default Description
decimalLength 0 Number of decimal places if the field is a numeric field
readOnly false Whether the field has read-only access
Event Parameters Description
none    
Method Parameters Description
none    

Remember these objects are created automatically by the Rowset object.

PdxField class

Properties

The following tables list the properties, events, and methods of the PdxField class. PdxField objects also contain those inherited from the Field class.

Property Default Description
lookupTable Empty string Table to use for lookup value
lookupType Empty string Type of lookup
maximum   Maximum allowed value for field
minimum   Minimum allowed value for field
picture Empty string Formatting template
required false Whether the field must be filled in
readOnly false Whether the field has read-only access
Event Parameters Description
none    
Method Parameters Description
none    

SQLField class

Properties

The following tables list the properties, events, and methods of the SqlField class. SqlField objects also contain those inherited from the Field class.

Property Default Description
precision   The number of digits of precision
scale   How the number is scaled
Event Parameters Description
none    
Method Parameters Description
none    


 Database Class vs. Set database

 

There are a few more classes available. These are not compulsory. If you are using DBF or DB tables on a standalone PC -- or on a file server -- you can just use a Query object.

However, your data may reside on an SQL server or another database/file format handled via an SQL Links or ODBC driver. In this case, you will need a way to hook to the database alias as identified in the BDE configuration.

The Database class will provide for such a link. The Database object will handle the task of the legacy OPEN DATABASE/SET DATABASE TO commands. It will also offer a number of methods and events, as we shall see later on.

Of course the Database Alias has to be defined with the BDE configuration utility before you can use it.

The following diagram summarises the evolution of the class/object hierarchy when using a Database object in a form.

As you may notice, several different Query objects can be linked to the same Database Object.

    MyForm = NEW Form()
    MyForm.Database1 = NEW Database()
    MyForm.Database1.DatabaseName = "MyDatabase"
    MyForm.Database1.Active = True
    MyForm.Query1 = NEW Query()
    MyForm.Query1.Database = MyForm.Database1
    MyForm.Query1.Sql = "SELECT * FROM 'MyTable' "
    MyForm.Query1.Active = True
    ...
    MyForm.Query1.Rowset.Next(-1)
    ? MyForm.Query1.Rowset.EndOfSet

    True

 

  

Properties

The following tables list the properties, events, and methods of the Database class.

Property Default Description
Active false Whether the database is open and active or closed
CacheUpdates false Whether to cache changes locally for batch posting later
ClassName Database Identifies the object as an instance of the Database class
DatabaseName Empty string BDE alias, or empty string for built-in database
DriverName Empty string Type (table format or server) of database
Handle   BDE database handle
LoginString Empty string User name and password to automatically try when opening database
Parent null Container form or report
Session Default session Session to which database is assigned
Event Parameters Description
None    
Method Parameters Description
AbandonUpdates( )   Discards all cached changes
ApplyUpdates( )   Attempts to post cached changes
beginTrans( )   Begins transaction; starts logging changes
commit( )   Commits changes made during transaction; ends transaction
copyTable( ) <source name expC>,
<destination name expC>
Makes a copy of a table in the same database
createIndex( ) <table name expC>,
<index oRef>
Creates an index for a table in the database
dropIndex( ) <table name expC>,
<index name expC>
Deletes an index from a table in the database
dropTable( ) <table name expC> Deletes table from database
emptyTable( ) <table name expC> Deletes all records from a table
executeSQL( ) <expC> Pass-through SQL statement
packTable( ) <table name expC> Removes deleted records from DBF or DB table and reconsolidates disk usage
reindex( ) <table name expC> Rebuilds indexes for DBF or DB table
renameTable( ) <source name expC>,
<destination name expC>
Renames table in database
Rollback( )   Undos changes made during transaction; ends transaction
TableExists( ) <table name expC> Whether or not specified table exists in database or on disk 

Once a database object is instantiated and linked to a BDE database alias, you can hook one (or more) Query objects to the database. These queries can of course use one or more tables from the database.

The Database object's Active property has to be set to True in order to activate the connection ("open" the database view).

If you are connecting to an SQL server, you may then use one or more StoredProc objects, instead of Query objects. StoredProc will enable hooking to the stored procedures within the SQL database.

 


Session class vs. Create Session

Above the Database object, and at the top of the hierarchy, comes the Session object.

Remember the dreadful CREATE SESSION ? Well, the new Session class replaces the fuzzy automatic built-in implicit form-related session behaviour. It brings along some events and methods as well. Most of all, it provides for a clearer identification of the sessions for the developer.

The session isolates all the dependent data-related components. Database/Query objects belonging to a different session will be linked to a different Session object. It's reasonably simple, understandable and traceable.

 

 

 

Properties

The following table lists the properties of the Session class.

Property Default Description
className Session Identifies the object as an instance of the Session class
handle   BDE session handle
lockRetryCount 0 Number of times to retry a failed lock attempt
lockRetryInterval 0 Number of seconds to wait between each lock attempt
parent null Container form or report
Event Parameters Description
none    
Method Parameters Description
access( )   Returns the user’s access level for the session
addPassword( ) <password expC> Adds a password to the password table for access to encrypted DB (Paradox) tables
login( ) <group expC>,
<user expC>,
<password expC>
Logs the specified user into the session to access encrypted DBF (dBASE) tables
user( )   Returns the user’s login name for the session

 

 

 

Here is a diagram summarising some possible component architecture using a session.

 

 


DataModule class vs. QBE file 

The DataModule class will enable to create an object to act as a container for some or all data-related components we have been examining. You then have to use only one component for the data in the form. One important advantage is also to provide for reusability in other forms / reports.

Without the DataModule, when you have defined a Query and its Database, as well as a Session, and set their properties accordingly for one form, you would have to do it all over again for subsequent forms using the same data setup.

Instead, you can define a DataModule to contain the aforementioned classes. This DataModule can then be included in future forms using the same data setup.

.

DataModRef

When done through the inspector, or in coding, the DataModule is declared in the form constructor code via a DataModuleRef component.

You can include several DataModuleRef components per Form if necessary.

This is somehow equivalent to declaring a QBE via the View property in Visual dBASE 5.x, but also a very different process in the same time. While the QBE contains DML for a collection of tables (related or not), we are talking here about objects which can be safely added and mixed without having to care about the whole architecture each time the system evolves.


Additional classes


 

CalcField class

The CalcField class will enable you to add a calculated field object to a Rowset. Once defined, a CalcField can be used the same way as another Field. The difference is that although it is datalinked, you cannot modify table data through a CalcField.

 

Syntax

[<oRef> =] new CalcField(<name expC>)

<oRef> A variable or property in which to store the reference to the newly created CalcField object.

<name expC> The name of the calculated field. It cannot duplicate the name of any other field in the query.

Properties

The following tables list the properties, events, and methods of the CalcField class.

Property Default Description
className CalcField Identifies the object as an instance of the CalcField class
fieldName   Name of the object
length 0 Maximum length
parent null fields array that contains object
type Character Identifies calculated field’s return type
value Empty string Current value of calculated field
Event Parameters Description
beforeGetValue   When value property is to be read; return value is used as value
onGotValue   After value is read
Method Parameters Description
none    

 


StoredProc class

Basically this class is a local representation for a stored procedure call in a database engine. It will be used to access a stored procedure in an SQL database.

The StoredProc object behaves in a similar way as the Query object. If the stored procedure returns a cursor of data, then the StoredProc object generates a Rowset (and Field) object(s).

Of course, unlike the Query, the StoredProc object must be linked to a Database object.

Syntax

[<oRef> =] new StoredProc()

<oRef> A variable or property—typically of a Form or Report object—in which to store a reference to the newly created StoredProc object.

Properties

The following tables list the properties, events, and methods of the StoredProc class.

Property Default Description
active False Whether the stored procedure is open and active or closed
className StoredProc Identifies the object as an instance of the StoredProc class
database null Database to which the stored procedure is assigned
handle   BDE statement handle
params AssocArray Associative array that contains Parameter objects for the stored procedure call
parent null Container form or report
procedureName Empty string Name of the stored procedure
rowset null Results of the stored procedure call
session null Session to which the stored procedure is assigned
Event Parameters Description
canClose   When attempting to close stored procedure; return value allows or disallows closure
canOpen   When attempting to open stored procedure; return value allows or disallows opening
onClose   After stored procedure closes
onOpen   After stored procedure first opens
Method Parameters Description
prepare()   Prepares stored procedure call
requery()   Rebinds and executes stored procedure

 


UpdateSet class

 

The UpdateSet class will do what its name implies: enable you to update data in a table with data from another table. The methods in the class will permit appending, updating, copying and deleting data according to matching keys.

 

Syntax

[<oRef> =] new UpdateSet( )

<oRef> A variable or property in which to store a reference to the newly created UpdateSet object.

Properties

The following tables list the properties, events, and methods of the UpdateSet class.

Property Default Description
changedTableName   Table to collect copies of original values of changed rows
className UpdateSet Identifies the object as an instance of the UpdateSet class
destination   Rowset object or table name that is updated or created
indexName   Name of index to use
keyViolationTableName   Table to collect rows with duplicate primary keys
problemTableName   Table that collects problem rows
source   Rowset object or table name that contains updates
Event Parameters Description
none    
Method Parameters Description
append( )   Adds new rows
appendUpdate( )   Updates existing rows and adds new rows
copy( )   Creates destination table
delete( )   Delete rows in destination that match rows in source
update( )   Update existing rows 

 

 


File class

 

The File class brings the functionalities of the low-level file functions in VdB 5.x.

Why would anyone use a File object instead of FOPEN() ?

Again, to benefit from the advantages of objects: ease of reusability, protection within changing programming environement (encapsulation) to name a few.The methods of the class offer functionalities similar to the low-level functions, plus some more.

 

Syntax

[<oRef> =] new File( )

<oRef> A variable or property in which to store a reference to the newly created File object.

Properties

The following tables list the properties, events, and methods of the File class.

Property Default Description
className File Identifies the object as an instance of the File class
handle -1 Operating system file handle
path   Full path and file name for open file
position 0 Current position of file pointer, relative to the start of the file
Event Parameters Description
none    
Method Parameters Description
accessDate( )   Returns the last date a file was opened
close( )   Closes the currently open file
copy( ) <filename expC>, <new name expC> Makes a copy of the specified file
create( ) <filename expC>
[,<access rights>]
Creates a new file with optional access attributes
createDate( ) <filename expC> Returns the date when the file was created
createTime( ) <filename expC> Returns the time a file was created as a string
date( ) <filename expC> Returns the date the file was last modified
delete( ) <filename expC> Deletes the specified file
eof( )   Returns true or false indicating if the file pointer is positioned past the end of the currently open file
error( )   Returns a number indicating the last error encountered
exists( ) <filename expC> Returns true or false to indicate whether the specified disk file exists
flush( )   Writes current data in the file buffer to disk and keeps file open
gets( ) [<chars read expN>]
[, <eol expC>]
Reads and returns a line from a file, leaving the file pointer at the beginning of the next line. Same as readln( )
open( ) <filename expC>
[,<access rights>]
Opens an existing file with optional access attributes
puts( ) <input string expC>
[,<max chars expN>
[, <eol expC>]
Writes a character string and end-of-line character(s) to a file. Same as writeln( ).
read( ) <characters expN> Reads and returns the specified number of characters from the file starting from the current file pointer position; leaving the file pointer at the character after the last one read
readln( ) [<chars read expN>]
[, <eol expC>]
Reads and returns a line from a file, leaving the file pointer at the beginning of the next line. Same as gets( ).
rename( ) <filename expC>
, <new name expC>
Changes the name of the specified file to a new name
seek( ) <offset expN>
[, 0 | 1 | 2 ]
Moves the file pointer the specified number of bytes within a file, optionally allowing the movement to be from the beginning (0), end(2), or current file position (1).
shortName( ) <filename expC> Returns the short (8.3) name for a file
size( ) <filename expC> Returns the number of bytes in the specified file
time( ) <filename expC> Returns the time the file was last modified as a string
write( ) <expC>
[, <max chars expN>]
Writes the specified string into the file at the current file position, overwriting any existing data and leaving the file pointer at the character after the last character written
writeln( ) <input string expC>
[, <max chars expN>
[, <eol expC>]
Writes a character string and end-of-line character(s) to a file. Same as puts( )

 


Synthesis


Here is a diagram summarising some possible component architecture. 

 

Here is another diagram for a possible implementation using the security features in the Session class:

 

Another implementation for a DataModule component could be as pictured in the following diagram :

And finally, the following table provides a summary to understand which components are involved when things are taking place :

Functionality Session Database Query Rowset Field
Login/Password
/User/LockRetry
DBF & DB

X

Login/Password
SQL databases

X

Isolation level
Transaction
CreateIndex
Table create, drop

X

SQL  statement

X

Can/On Open/Close

X

Edit/Filter/Locate
Active index
Can/On Navigate
First/Last/Next

X

Fields properties
Can/On Change
Before/On GotValue
Copy/Replace BLOBs

X

 


The final word


The data-related components which come with the 32-bit version of Visual dBASE provide for the final set of features which make out of this product a fully object-oriented development environment.

The dBASE developer will have to exercise some effort to grasp the new way of handling tables and data. 

However, after you gain some comfort from practicing the new features, you will benefit from the highly increased power. Soon you will wonder how you were able to live for so long with the limitations of the good old work area model.

François Ghoche - July 97 


ATON consulting François Ghoche can be reached at :
E-mail fgwebfghoche.com;
CompuServe 100023,24 ;
mail : ATON - BP 49 - 78042 Guyancourt (France);
voice : +33 (0) 139 440 301;
fax : +33 (0) 139 440 331;
World Wide Web : http://www.fghoche.com.
V.I.P. Group