-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 1
Session E-CAD Cursor adapter - powerful tool to encapsulate data
layer in N-tier
applications
Venelina Jordanova
Introduction Developing wide range of applications we always
need to consider not only user interface, but also data model and
data access. Designing data layer the developer has to choose the
best database platform for the application, but he has always to
keep in mind that the application which will proceed a few thousand
records in single user mode in next years could expand to a large
scale application, handling hundreds of thousands records. In such
case you are often enforced to migrate application data toward more
vigorous data storage. This often can lead to a need to fully
rewrite applications data access.
The approach used to access data is basic matter to avoid such
fundamental application amendments, when data storage needs to be
changed.
In this session we will examine how CursorAdapter class can help
developers to organize applications data access in such way, so
future data storage changes (and respectively data source changes)
to have minimal impact on it.
-
10th European Visual FoxPro DevCon 2003 Cursor adapter -
powerful tool 2 E-CAD (Group DATA) 2003 Venelina Jordanova
Using CursorAdapter class The CursorAdapter acts as a middleware
between source of data and a VFP cursor. It is responsible for
creation cursors, so your business logic and user interface layers
use these cursors and do not care about the way in which data is
retrieved. I list below some most important properties, methods and
events of the class.
DataSourceType is the property that determines how data will be
retrieved. It sets the overall behavior of the class.
DataSource property is used when DataSourceType is set to ODBC
or ADO. In case you use an ODBC DataSourceType, you have to set
DataSource to a valid ODBC connection handle. You can use
SQLConnect() or SQLStringConnect() functions to establish an ODBC
connection. If you use ADO DataSourceType, DataSource property must
be set to an ADODB.RecordSet object and its ActiveConnection
property, in turn, must be set to a valid open ADODB.Connection.
These objects you have to be created and initialized manually. You
can also use the CursorAdapter Builder, which will write a lot of
necessary code for you.
Alias property holds the name of the resultant cursor. You
always have to keep in mind that the cursor is associated with the
CursorAdapter object and exists along with it. So you have to
ensure that appropriate CursorAdapter object exists in order to use
the created cursor.
The property UseDEDataSource gives you a possibility to use
DataEnvironments properties DataSourceType and DataSource. You can
leave blank the correspondent CursorAdapter properties, when you
set this property to True.
SelectCmd is a command sent to data storage to retrieve data. It
must conform syntax of the particular data storage. If you plan to
use different data source types using common CursorAdapter classes,
you must conform their syntax and use appropriate commands or write
different code for every particular case.
The CursorSchema property must contain description of the
structure of the resultant cursor in format appropriate for CREATE
CURSOR command. You can leave this property blank, but in that case
you cannot use the cursors structure at design time. Another
helpful feature of CursorSchema is that you can force data type
conversions between data source types and cursor fields types (for
example datetime fields to date data type). Note that the length of
the schema cannot exceed 255 characters if you want to set this
property directly. If you need to store longer schema string you
have to make this in code.
Tables, KeyFieldList, UpdatableFieldList and UpdateNameList are
required properties if you want the CursorAdapter to automatically
update the data. The base difference between last two mentioned
properties is that UpdatableFieldList contains names of fields that
will be updated back to the data source and UpdateNameList holds a
list that matches cursors fields and tables fields. Keep in mind
that to be sent properly your updates to data storage, you always
have to include the primary key field names in the UpdateNameList.
And because these are key fields and you do not want it to be
updated, you will not include them in UpdatableFieldList.
The ConversionFunc property specifies conversion functions that
will be applied when automatic updating is performed.
*Cmd, *CmdDataSource, *CmdDataSourceType properties give you
more control over the data updates.
CursorFill(lUseCursorSchema, lNoData, nOptions, Source) method
creates the cursor and fills it with data. When lUseCursorSchema is
True, it uses the schema in the CursorSchema property to create the
cursor. Otherwise it creates the cursor using the data types as
normally determined by Visual FoxPro. If lNoData parameter is True
an empty cursor will be created. The parameter nOptions specifies
additional flags for creating the cursor. These values are also
used for the CursorRefresh method. The Source parameter holds a
reference to an ADO Command or an open RecordSet object.
CursorRefresh() method re-executes the select command.
CursorAttach(cAlias, lInheritCursorProperties) and
CursorDetach() methods allow you to attach an existing cursor to a
CursorAdapter object or to free the cursor associated with the
CursorAdapter. Attaching a cursor sets it under CursorAdapter
control and the CursorAdapter is responsible for further updates to
data source and closing the cursor when CursorAdapter is destroyed.
If you need a cursor to exist after CursorAdapter is destroyed or
you want it no longer to be under CursorAdapters control you can
free the cursor using CursorDetach method.
Before* and After*() events are very helpful if you need to
control the behavior of CursorAdapter object more deeply. On before
events you can change the SQL command that will be executed and
even cancel the action.
-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 3
You can also use AfterCursorFill event to create necessary
indexes. All of insert and update events receive parameters that
give you information what happened in the data. cFldState parameter
field and row state as you would receive from GETFLDSTATE(-1)
function.
To make easier work with new CursorAdapter class VFP 8 includes
a new CursorAdapter Builder. It helps you quick to set
CursorAdapters properties and also generates necessary code for
you. In addition to this we also have a new DataEnvironment Builder
that helps to set Data Source properties for the DataEnvironment.
This is also the place, where you add CursorAdapter objects to
DataEnvironment. This will be discussed later in this article.
Sample application An important part of this article is a small
sample application that is designated to show how CursorAdapter
class can save your time and efforts when migrating data between
different data storage platforms.
The sample application consists of a form that combines
presentation and business logic layers and a set of data access
classes based on CursorAdapter class. Used database is well-known
Northwind database, as far as it comes with Visual FoxPro 8 and MS
SQL Server installations. The form presents orders and detail lines
for particular order. It uses updateable cursors created from
Orders and OrderDetails tables and a set of lookup cursors
corresponding to Employee, Customers and Shippers tables.
DataAccess is a class library that contains several
CursorAdapter classes used to access applications data.
Figure 1. Data access classes
To run the application you need to access Northwind database for
every different data source type that you want to use. For Native
VFP data it is located in Northwind folder under _samples path. It
also ships as a sample database with MS SQL Server and MS
Access.
Developing data access classes
Underlying base class The ground of well-designed data access is
thoroughly planed structure of classes. Specifics of every level
have to be encapsulated in such way, so any necessary changes not
to enforce changes in inherited classed.
We will base our data access on one underlying class, which will
be responsible for common data access. This base class will be
changed later when it is necessary to access different data
sources. You can set the properties of the class yourself or using
the builder. CursorAdapter Builder generates code to build
connection and also fills most of the properties that you need to
use.
Our underlying class (see Figure 1) will hold connection
information that will be common for all classes that will inherit
it and have dummy cursor name, which will be overwritten in every
particular subclass.
In builder you can also set whether CursorAdapter will use
DataEnvironments data source. In this case we will not use it and
here we must fill connection information.
-
10th European Visual FoxPro DevCon 2003 Cursor adapter -
powerful tool 4 E-CAD (Group DATA) 2003 Venelina Jordanova
In the beginning our application will use VFP Native data. In
the Data source type drop-down list we choose Native and then set
the Database.
For some reasons you may not want the Builder to generate code
for the connection. Then connection setting will be used
temporarily only in the builder.
In the Data Access page Data fetching and Buffering mode
properties have to be set and all other Data access properties are
left blank. All of them will be further defined for every
particular subclass.
Figure 2. Defining the base data access class caBaseAccess
Data access classes For our sample application we will create
two CursorAdapter classes that will read and update data and
another four CursorAdapter classes that will be responsible to
create lookup cursors, all of them based on caBaseAccess.
Lets name the CursorAdapter class that will retrieve Orders data
caOrders. It is based on caBaseAccess and inherits all its
properties.
Figure 3. Inheriting caBaseAccess
-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 5
Setting properties related to data access All properties
concerning data source must keep their default values. When in the
future data access needs to be changed these properties will be
changed only in the underlying class. For this newly created class
we have to set table specific properties: Select command, Cursor
Schema, Tables UpdateNameList and UpdatableFieldList. You can also
use the builder to generate the code.
In the Data Access page (Figure 4) we can specify the Select
command and Cursor Schema.
Figure 4. Data Access page is used to specify Select command and
Cursor Schema along with Data fetching and Buffering mode.
The Select Command Builder (Figure 5) can help us easy and fast
to create simple SELECT statements.
Figure 5. Select Command Builder provides visual interface for
creating SQL SELECT statement
The necessary table has to be chosen from the drop-down list and
you can move the fields that you need on the right side, where
selected fields are listed. In the same way as defining views, you
can also move the first available choice * that means that all
fields from the source table will be selected. If you use Native
data source type, you are also allowed to add additional tables by
clicking the Add Tables button. Often this could be free tables
that are not included in the database. Clicking on OK will build
the appropriate SELECT statement.
If you click on Build button for the Cursor Schema it is
automatically generated using the specified Select command. In this
moment the builder instantiates a cursor adapter and calls its
CursorFill method to populate the cursor. In order to work this
feature needs to have access to your data otherwise the cursor
could not be created. You can also write the Cursor Schema
yourself.
-
10th European Visual FoxPro DevCon 2003 Cursor adapter -
powerful tool 6 E-CAD (Group DATA) 2003 Venelina Jordanova
If we used the Select Command Builder, Tables property is
already filled on the Auto-Update page (see Figure 6). Send updates
option is set by default. If you want data not to be sent back to
data source you can uncheck it. Auto-update checkbox determines
whether CursorAdapter will automatically generate update
statements. In fields grid we see fields mapping (also
automatically generated) and we have to choose key fields (key
symbol column) and fields that will be automatically updated
(pencil symbol column).
Figure 6. Auto-Update page is used to specify how update
statements will be generated.
Here we can also define conversion functions, which will be used
for certain fields transformation before to send data to data
source. You can also choose which values will be used for
automatically WHERE clause generation.
Finally we have to write code that performs additional actions
according application needs. For example in the AfterCursorFill
event we will write code to create necessary indexes. These indexes
will be used later in forms to ensure proper data viewing in user
interface forms or for building relationships between cursors. It
is also possible here to perform some calculations or data
conversions that cannot be performed at data storage side. An
example for such calculation is a resultant field containing the
number of overdue days for a bill (or an invoice).
When you access Native data and your fields are of DATE data
type this can be as simple expression as:
SELECT InvoiceId, InvoiceDate, iif(DATE() > DueDate, DATE()
DueDate, 0) as OverdueDays from Invoice
When your fields are of DATETIME data type command will look
like this:
SELECT InvoiceId, InvoiceDate, iif(DATE() > TTOD(DueDate),
DATE() TTOD(DueDate), 0)
as OverdueDays
-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 7
from Invoice Finally if you access a SQL Database trough ODBC
data source your select command:
SELECT InvoiceId, InvoiceDate, case when DATEDIFF(dd, DueDate,
GETDATE()) > 0 then DATEDIFF(dd, DueDate, GETDATE()) else 0 end
as OverdueDays from Invoice
To apply such functionality you will either need to write
complex code to assign different values to SelectCmd depending of
DataSourceType or you can perform data manipulation in
AfterCursorFill event. In this case the SelectCmd will look in this
way:
SELECT InvoiceId, InvoiceDate, DueDate, 0 as OverdueDays from
Invoice
Cursor schema will determine the scale of resultant OverdueDays
field: INVOICEID I, INVOICEDATE D, INVOICEDATE D, OVERDUEDAYS
N(3)
This will create an empty field where in AfterCursorFill you can
store calculated number of days later using similar code:
CurrentDate = DATE() RELPACE ALL OverdueDays WITH IIF(DueDate
> CurrentDate, DueDate - CurrentDate, 0)
Which one of the above approaches is better for your application
depends on several factors. It depends on type and complexity of
calculations that you need to perform, on amount of processed data
rows. For some purpose you may prefer to write simple data
conversions at the client side, but for heavy data processing you
might prefer to use server side stored procedures. Concerning
accessing different data sources you could prefer to processes data
using common FoxPro code independent of data storage specifics.
However, when complex online analyzing processing on large data is
needed stored procedures that are optimized and cached at server
side must always be considered.
Handle update conflicts Not long ago developers needed to append
to DELETE and UPDATE commands additional statements to force error
rising if a conflict occurs. When Key and modified fields are used
in WHERE clause and more than one user modify one and the same
record and same fields, the user that will commit his changes later
will not succeed to write them into the database, because no
records will correspond to generated WHERE clause. In same time
TABLEUPDATE() function always returns .T. because no error were
encountered. The same problem can appear when deleting records.
Starting with VFP 8 Service Pack 1 developers have additional
properties for better update conflicts handling. Two new properties
are added to CursorAdapter class, which are designated to determine
how data updating errors will be processed.
The ConflictCheckType property specifies how the conflict checks
are handled during an update or delete operation. You have four
different alternatives
Value Description 0 Do not perform check. (Default) 1 In a
single row update mode, check for update conflicts during a SQL
UPDATE or
DELETE operation. If conflict occurs, specifically, when less
than one record is affected by any command specified by UpdateCmd
or DeleteCmd property, return error "Update conflict (Error
1585)".
2 In a single row update mode, check for key uniqueness during a
SQL UPDATE or DELETE operation. If more than one record is affected
by any command specified by the UpdateCmd or DeleteCmd property,
return message "Warning: The key defined by the KeyField property
for table "alias" is not unique. (Error 1495)"
3 Perform checks as specified by setting 1 and 2. 4 Append
custom command specified by ConflictCheckCmd property to the
commands in the UpdateCmd and DeleteCmd properties.
-
10th European Visual FoxPro DevCon 2003 Cursor adapter -
powerful tool 8 E-CAD (Group DATA) 2003 Venelina Jordanova
If the ODBC driver or OLE DB Provider is unable to provide
required functionality or if the functionality is disabled,
settings 1, 2, and 3 for ConflictCheckType might fail. For example
is SET NOCOUNT ON in SQL Server. In this case no information about
affected records is available.
If you set ConflictCheckType to 4 CursorAdapter uses
ConflictCheckCmd property. Its value is appended to the commands
specified by the UpdateCmd and DeleteCmd properties for checking
update or delete conflicts. For example when working with Native
data source you can write a function that checks conflicts using
_tally system variable. If your data storage is SQL Server, you can
use @@ROWCOUNT and @@ERROR system functions like this:
ConflictCheckCmd="IF @@ERROR=0 AND @@ROWCOUNT!=1 RAISERROR ('
Update conflict!', 16, 1)"
Customizing Select statement In the same way we define all other
CursorAdapter classes that we have planed to use. For those of them
that are proposed to be used as lookup cursors we can set that
updates will not be send. Furthermore we can use calculated fields
in select commands. For example lets plan to have a drop-down combo
box that will be used to choose employee for particular order. If
we use Select command builder as usual (see Figure 7), we will have
two separate fields for FirstName and LastName columns.
Figure 7. Using Select Command Builder to construct select
statement
When we present them in a drop-down list it is more suitable to
have one common column EmployeeName that will be resultant of
FirstName and LastName. To accomplish this, we will amend the
generated select statement in that way:
select RTRIM(EMPLOYEES.FIRSTNAME) + " " +
RTRIM(EMPLOYEES.LASTNAME) as EmployeeName, EMPLOYEES.EMPLOYEEID
from EMPLOYEES
When we change the select command we have to press Build Schema
button again the builder will automatically generate correct cursor
schema. (Figure 8)
-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 9
Figure 8. Customizing Select statement to suit particular
requirements
It is very important when you plan to use different data sources
to write SELECT statement compliant with all of them. In this
particular case I used RTRIM function instead of TRIM or ALLTRIM
functions, because it is a common for VFP and T-SQL and when we
switch to SQL Server data source in the future this CursorAdapter
class will remain useful without any changes in it.
Unfortunately amending Select command in this way does will
violate Select Command Builder and you cannot invoke it again and
add new fields. So you can amend Select command when you will no
longer need Select Command Builder help.
Developing user interface and business logic layers In our
sample application these two layers are unified to simplify this
part, as our main goal here is to create a portable data layer. In
your real-life application you will split these layers according to
your needs and application architecture.
Lets start with creating an empty form and name it frmOrders.
You can use the well-known feature to add controls on the form by
simply drag fields from DataEnvironment cursors onto the form. For
this purpose we have to create objects based on our data access
classes into the DataEnvironment of the form. We will make this by
using the DataEnvironment Builder. As far as we want to encapsulate
data layer in separate classes, we will not use DataEnvironments
data source properties. Data Source page can remain blank. On
Cursors page we will create all CursorAdapter objects necessary for
the form. There are four buttons below Cursor Adapters list that
allow you to add a new object based on a specified class or to
create a new CursorAdapter object, as well as to remove the object
or to invoke Builder for the selected CursorAdapter.
Add button invokes Open dialog where you should choose
CursorAdapter class to be used for creating new CursorAdapter
object (Figure 9).
-
10th European Visual FoxPro DevCon 2003 Cursor adapter -
powerful tool 10 E-CAD (Group DATA) 2003 Venelina Jordanova
Figure 9. Adding CursorAdapter class to forms
DataEnvironment
After choosing the class, a Builder form is invoked and you can
change setting for the newly created object. In this way we create
CursorAdapter objects for all data needed for the form and
associated cursors are visible in the DataEnvironment Designer
(Figure 10.)
Figure 10. DataEnvironment of frmOrders
Here, in the DataEnvironment we can also write code in
CursorAdapter objects events to prepare forms specific indexes and
other actions that related to the particular form.
For this sample application I choose to load all data at once.
In this case it is necessary in the forms Init method to establish
a relationship between crsOrders cursor and crsOrderDetails
cursor.
In case you will need to proceed large amount of data, loading
entire table content in cursors is not a good solution. In such
case you could retrieve at whole only parent table (crsOrders) and
retrieve child records (crsOrderDetails) for every parent record.
You could even decide to retrieve Orders row by row and load off
clients computer resources. For this type of data retrieval you
should use parameterized queries. Parameters are passed in a way
that you already know from pass-trough queries.
Now after we have created all necessary CursorAdapter objects in
the forms DataEnvironment, it is fast and easy to create controls
on the form just by dragging fields onto it.
On Figure 11 you can see how the form looks like after creating
controls and adding a navigation bar as well as a few drop-down
combo boxes that are designated to give users an easier way to fill
customer, shipper and employee data. For same purpose in the
grdOrderdDtails grid is also added a column colProductName.
-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 11
Figure 11. Form designer - frmOrders
And finally we have to add code to fill OrderId field in
crsOrderDetails, to ensure referential integrity when adding new
detail records in the grid.
Data updating considerations Now our form can load data and
navigate trough data records. We can run it to ensure that it runs
correctly. At this stage all data changes that a user makes using
our form are still hold only in the cursors.
Its time to manage saving data back to the data storage!
Updating data for existing orders is straight forward just use
TABLEUPDATE() function and all changes are committed. The more
complicated task is to manage newly added orders as far as OrderId
field is autoincrementing (identity in SQL Server). So, in this
case the CursorAdapter should not include it in the generated
INSERT statement and later we have to perform additional actions to
obtain the ID that has been assigned to our newly added record.
When retrieving this newly generated Id value we have to keep in
mind that if application runs in multi-user environment there is no
guarantee that this will be the maximum ID value in the table. In
case we are working with Native Data source type this is currently
inserted record in the table. If we are using ODBC data source type
to connect to MS SQL database or MS Access database, the way to
have this value back is to query the system function @@IDENTITY.
Note that for MS Access this feature is available in Access 2000
and later.
Next issue that we have to address is the fact that this newly
obtained Id has to be written for all child records in
crsOrderDetails, so that subsequent TABLEUPDATE() for
crsOrderDetails to write correct values.
Another very important issue when working with remote data is
generating ID values when we need to add more than one record in
parent cursor (crsOrders in our case) in same time and then to
update all of them. Recommended practice in such cases is to write
negative ID values. Why shall we do this? Lets think about a
-
10th European Visual FoxPro DevCon 2003 Cursor adapter -
powerful tool 12 E-CAD (Group DATA) 2003 Venelina Jordanova
multi-user application where two users are entering orders
simultaneously. They both start the application and retrieve
existing data records. If the last used ID at this time is 2048,
both of them will create records starting with 2049 in their
cursors. What will happen if the first one entered two orders and
then wants to save the data, but meanwhile the second user has
already saved one order record? In crsOrders we will have two new
records, numbered respectively 2049 and 2050. In scrOrderDetails
will be also several records numbered with same OrderID values.
After inserting these new records into data source, new ID values
will be respectively 2050 and 2051. Therefore our first task
afterwards should be to replace OrderId with 2050 in all
crsOrderDetails records that till this moment were numbered 2049.
Right in this moment we have mixed order details records for both
new records, as far as all they contain OrderId value 2050. The
only way to avoid this problem is to use ID values that could never
come across real used values. Using negative numbers as ID values
for newly added records can ensure that these values can never be
mixed with IDs generated when data records are inserted into data
source.
Considering all mentioned above, in AfterInsert method we will
write additional code to handle this. For this purpose I added a
new property to the caOrders class that will hold the name of
corresponding child alias, so the class itself not to require hard
coded child cursors name. The code in AfterInsert method that will
ensure referential integrity will look like this:
LPARAMETERS cFldState, lForce, cInsertCmd, lResult Local
lnOldIDValue, lcFilter, lnNewId If lResult lnNewId = 0 DO case Case
Upper(This.DataSourceType) == "NATIVE"
* this code relies of fact that when working with * Native data
source type, the table remains opened and * record pointer is on
newly inserted record
lnNewId = Orders.OrderId Case InList(Upper(This.DataSourceType),
"ODBC", "ADO") If SQLExec(This.DataSource, ; "Select @@IDENTITY as
NewIDValue", "crsNewID") > 0 lnNewId = crsNewID.NewIDValue USE
in crsNewID EndIf Otherwise EndCase If lnNewId > 0 lnOldIDValue
= Evaluate(Alltrim(This.Alias)+".OrderID") Select
(This.cChildAlias) Replace all OrderId with lnNewId for OrderId =
lnOldIDValue Select (This.Alias) Replace OrderId with lnNewId Else
MessageBox("Error retreiving ID value", 0 + 16, "Error") endif
endif
After making these final polishing, our sample application is up
and running and its real life begins.
Application growing After a couple of months flawlessly work,
suddenly appears a need to upsize the database to SQL Server. There
could be many different reasons: optimizing data processing or by
security reasons. So, we need to migrate the data and upgrade our
application to access SQL Server database. Thanks to our multi-tier
application structure, this renovation will have minimal impact to
our code. Fortunately we will need to make changes in data access
classes only. Even more we will need to alter only our underlying
base class caBseAccess.
Data access reorganization The most popular approach to access
SQL Server data from VFP applications is to use ODBC connections
and we will also use it. All we need is to change value of
DataSourceType property and to assign a valid ODBC connection
handle to DataSource property. Note that you have to manage
connection yourself. We can manually handle establishing connection
or use the CursorAdapter Builder to help us (see Figure 12). It is
always good practice to test your connection to ensure that
connection string is properly written and that the connection can
be established.
-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 13
Figure 12. Setting ODBC data source for caBaseAccess class
In fact CursorAdapter Builder does our work and has written
necessary code for assigning DataSource value. We can see this code
in the Init method of the class.
This.DataSource = sqlstringconnect([DRIVER={SQL SERVER};] + ;
[SERVER=JEI-Server;] + ; [Trusted Connection=ON;] + ;
[DATABASE=NORTHWIND;])
In case we have designed tables structure considering future
data migrating this step will be the only one necessary action to
change data access from using native tables toward using ODBC data
source.
Table name and field type considerations For a pity Northwind
databases that I use for sample data are not quite identical.
OrderDetails table is named slightly different in SQL Servers
Northwind database Order Details. Unfortunately this space in its
name it dramatic obstacle for caOrderDetails class. We need to open
the class and manually to change SelectCmd to the following:
select * from [ORDER DETAILS] Because fields names and type are
same and we do not need to change CursorSchema of the class, but we
need also to amend KeyFieldList and UpdatableFieldList properties.
Figure 13 shows how this is done using the builder. You can also
manually amend the code in the Init method of the class.
-
10th European Visual FoxPro DevCon 2003 Cursor adapter -
powerful tool 14 E-CAD (Group DATA) 2003 Venelina Jordanova
Figure 13. Setting caOrderDetails class according Northwind
database on SQL Server
Migrating data from one type data storage to another must be
thoroughly planed. All data types that dont quite match have to be
considered. For example the logical data type does not have exactly
correspondent data type among SQL Server data types, because Bit
data type is presented as N(1) when retrieved into a VFP cursor. In
order to have reusable code you can either use additional code to
convert different data types or you have to consider using of data
types that are identical in the set of data types for different
data storages that you intend ever to use.
Accessing heterogeneous data It often happens in real world that
newly developed application needs to use particular data, stored by
another existing application. In this case developers are often
stuck to the data source of the existing application. This fact
limits opportunities to choose most relevant data storage and
forces developers to use same data source in order to simplify data
access. For example in our sample application we could need to
obtain employees data from an existing payroll application. An
imaginary example of heterogeneous data can be the following
situation: Order entry system developed to use native VFP database,
that needs to read employees data from an Access database, keeping
in mind that it is planed to migrate payroll application toward SQL
Server database. As a developer you have to think how to access
Native, ADO and ODBC data source types so that any future data
migrations not to cause full application redeveloping.
CursorAdapter gives the developers admirable chance to organize
data access as independently as possible. Using CursorAdapter
classes, data layer can be based on several underlying classes,
each one responsible for accessing particular data source (see
Figure 14a). Then next level classes, inherited from these base
classes are build. Every one specific class holds table specific
properties and inherits data source properties. As we already have
seen associated cursors are filled according CursorAdapters
settings and presentation layer and business logic layer operate
with this cursors. When future needs require data migrating you can
either simply change data source properties of a particular
underlying class or you could just change the parent class of a
particular CursorAdapter subclass (Figure 14b).
-
Cursor adapter - powerful tool 10th European Visual FoxPro
DevCon 2003 2003 Venelina Jordanova (Group DATA) E-CAD 15
a) b)
Figure 14. Accessing heterogeneous data
Building a base set of underlying CursorAdapter classes gives
you also the flexibility to change data source type
programmatically and to develop applications that can access
different data source type according customers requirements.
Summary Being one of the most impressive enhancements in VFP 8,
Cursor adapter gives the developers great opportunity to develop
flexible reusable data classes. It is vigorous class that helps you
easy and rapidly to develop data layer in your applications. Using
CursorAdapter classes can significant cut down development
resources and efforts when migrating data to different data
storage.