SQL passthrough (SPT) functions, such as SQLCONNECT(), SQLEXEC(), and SQLDISCONNECT(), which are also based on ODBC connections.

Size: px
Start display at page:

Download "SQL passthrough (SPT) functions, such as SQLCONNECT(), SQLEXEC(), and SQLDISCONNECT(), which are also based on ODBC connections."

Transcription

1 Data Strategies in VFP: Introduction By Doug Hennig Overview There are a lot of ways you can access non-vfp data (such as SQL Server) in VFP applications: remote views, SQL passthrough, ADO, XML... This document will examine the pros and cons of different mechanisms and discuss when it s appropriate to use a particular strategy. We ll also look at an exciting new technology in VFP called CursorAdapter that will make accessing remote data much easier than it is in earlier versions. Introduction More and more VFP developers are storing their data in something other than VFP tables, such as SQL Server or Oracle. There are a lot of reasons for this, including fragility of VFP tables (both perceived and actual), security, database size, and corporate standards. Microsoft has made access to non-vfp data easier with every release, and has even been encouraging it with the inclusion of MSDE (Microsoft Data Engine, a free, stripped-down version of SQL Server) on the VFP 7 CD. However, accessing a backend database has never been quite as easy as using VFP tables. In addition, there are a variety of mechanisms you can use to do this: Remote views, which are based on ODBC connections. SQL passthrough (SPT) functions, such as SQLCONNECT(), SQLEXEC(), and SQLDISCONNECT(), which are also based on ODBC connections. ActiveX Data Objects, or ADO, which provide an object-oriented front end to OLE DB providers for database engines. XML, which is a lightweight, platform-independent, data transport mechanism. Which mechanism should you choose? The answer (as it is for the majority of VFP questions <g>) is it depends. It depends on a number of factors, including your development team s experience and expertise, your infrastructure, the needs of the application, anticipated future requirements, and so on. Let s take a look at each of these mechanisms, including their advantages and disadvantages, some techniques to be aware of, and my opinion of where each fits in the overall scheme of things. We ll also take a look at what I think is one of the biggest new features in VFP 8, the CursorAdapter class, and how it makes using remote data access via ODBC, ADO, or XML much easier and consistent.

2 Remote Views Like a local view, a remote view is simply a pre-defined SQL SELECT statement that s defined in a database container. The difference is that a remote view accesses data via ODBC (even if the data it s accessing is VFP) rather than natively. You can create a remote view either programmatically using the CREATE SQL VIEW command or visually using the View Designer. In both cases, you need to specify an ODBC connection to use. The connection can either be an ODBC data source (DSN) set up on your system or a Connection object that s already been defined in the same database. Here s an example that creates a remote view to the Customers table of the sample Northwind database that comes with SQL Server (we ll use this database extensively in our examples). This was excerpted from code generated by GENDBC; there s actually a lot more code that sets the various properties of the connection, the view, and the fields. CREATE CONNECTION NORTHWINDCONNECTION ; CONNSTRING "DSN=Northwind SQL; UID=sa; PWD=testdb; " + ; "DATABASE=Northwind; TRUSTED_CONNECTION=No" CREATE SQL VIEW "CUSTOMERSVIEW" ; REMOTE CONNECT "NorthwindConnection" ; AS SELECT * FROM dbo.customers Customers DBSetProp('CUSTOMERSVIEW', 'View', 'UpdateType', 1) DBSetProp('CUSTOMERSVIEW', 'View', 'WhereType', 3) DBSetProp('CUSTOMERSVIEW', 'View', 'FetchMemo',.T.) DBSetProp('CUSTOMERSVIEW', 'View', 'SendUpdates',.T.) DBSetProp('CUSTOMERSVIEW', 'View', 'Tables', 'dbo.customers') DBSetProp('CUSTOMERSVIEW.customerid', 'Field', 'KeyField',.T.) DBSetProp('CUSTOMERSVIEW.customerid', 'Field', 'Updatable',.F.) DBSetProp('CUSTOMERSVIEW.customerid', 'Field', 'UpdateName', ; 'dbo.customers.customerid') DBSetProp('CUSTOMERSVIEW.companyname', 'Field', 'Updatable',.T.) DBSetProp('CUSTOMERSVIEW.companyname', 'Field', 'UpdateName', ; 'dbo.customers.companyname') One of the easiest ways you can upsize an existing application is using the Upsizing Wizard to create SQL Server versions of your VFP tables, then create a new database (for example, REMOTE.DBC) and create remote views in that database that have the same names as the tables they re based on. That way, the code to open a remote view will be exactly the same as that to open a local table except you ll open a different database first. For example: if oapp.luselocaldata open database Local else open database Remote endif use CUSTOMERS If you re using cursor objects in the DataEnvironment of forms and reports, you have a little bit of extra work to do because those objects have a reference to the specific database you had selected when you dropped the views into the DataEnvironment. To handle this, put code similar to the following into the BeforeOpenTables method of the DataEnvironment:

3 local loobject for each loobject in This.Objects if upper(loobject.baseclass) = 'CURSOR' and not empty(loobject.database) loobject.database = iif(oapp.luselocaldata, 'local.dbc', 'remote.dbc') endif upper(loobject.baseclass) = 'CURSOR'.. next loobject One thing to be aware of: When you open a view, VFP attempts to lock the view s records in the DBC, even if it s only briefly. This can cause contention in busy applications where several users might try to open a form at the same time. Although there are workarounds (copying the DBC to the local workstation and using that one or, in VFP 7 and later, using SET REPROCESS SYSTEM to increase the timeout for lock contention), it s something you ll need to plan for. There s quite a controversy over whether remote views are a good thing or not. If you re interested in reading about the various sides of the arguments, check out these links: Advantages The advantages of remote views are: You can use the View Designer to create a remote view visually. Okay, until VFP 8, which fixes many of the known problems with the View Designer (especially with complex views involving more than two tables), this wasn t necessarily an advantage <g>, but even in earlier versions, simple views can be created very quickly and easily. It s great to visually see all the fields in the underlying tables, easily set up the various parts of the SQL SELECT statement using a friendly interface, and quickly set properties of the view using checkboxes or other UI elements. From a language point-of-view, remote views act just like tables. As a result, they can be used anywhere: you can USE them, add them to the DataEnvironment of a form or report, bind them to a grid, process them in a SCAN loop, and so forth. With some of the other technologies, specifically ADO and XML, you have to convert the result set to a VFP cursor before you can use it in many places in VFP. It s easier to convert an existing application to use remote views, especially if it already uses local views, than using any of the other techniques. Because you can add a remote view to the DataEnvironment of a form or report, you can take advantage of the visual support the DE provides: dragging and dropping fields or the entire cursor to automatically create controls, easily binding a control to a field by selecting it from a combobox in the Properties Window, and so on. Also, depending on the settings of the

4 AutoOpenTables and OpenViews properties, VFP will automatically open the remote views for you. It s easy to update the backend with changes: assuming the properties of the view have been set up properly, you simply call TABLEUPDATE(). Transaction processing and update conflict detection are built-in. Remote views are easy to use in a development environment: just USE and then BROWSE. Disadvantages The disadvantages of remote views are: Remote views live in a DBC, so that one more set of files you have to maintain and install on the client s system. Since a remote view s SQL SELECT statement is pre-defined, you can t change it on the fly. Although this is fine for a typical data entry form, it can be an issue for queries and reports. You may have to create several views from the same set of data, each varying in the fields selected, structure of the WHERE clause, and so forth. You can t call a stored procedure from a remote view, so a remote view needs direct access to the underlying tables. This may be an issue with your application s database administrators; some DBAs believe that data access should only be via stored procedures for security and other reasons. Also, because they are precompiled on the backend, stored procedures often perform significantly faster than SQL SELECT statements. When you use TABLEUPDATE() to write changes in the view to the backend database, you have little ability (other than by setting a few properties) to control how VFP does the update. As is the case with local views, if you use a SELECT * view to retrieve all the fields from a specific table and the structure of that table on the backend changes, the view is invalid and must be recreated. They work with ODBC only, so they re stuck in the client-server model of direct data connections. They can t take advantage of the benefits of ADO or XML. As I mentioned earlier, DBC lock contention is an issue you need to handle. Like other types of VFP cursors, you can t pass the result set outside the current data session, let alone to another application or tier. This limitation alone pretty much makes them useless in an n-tier application. Until VFP 8, which allows you to specify the connection handle to use when you open a remote view with the USE statement, you had little ability to manage the connections used by your application.

5 The connection information used for a remote view is hard-coded in plain text in the DBC (notice the user name and password in the code shown earlier). That means that a hacker can easily discover the keys to your backend kingdom (such as the user name and password) using nothing more complicated than Notepad to open the DBC. This isn t much of an issue starting in VFP 7 because it allows you to specify a connection string when you open a remote view with the USE command, meaning that you can dynamically assemble the server, user name, and password, likely from encrypted information, just before opening the view. Basically, it comes down to a control issue: remote views make it easy to work with backend data, but at the expense of limiting the control you have over them. When to Use Remote views are really only suited to client-server, 2-tier applications where you have a direct connection to the data. I believe that in the long run, most applications will be more flexible, robust, and have a longer shelf life if they re developed using an n-tier design, so remote views are best suited to those cases where you re upsizing an existing application and don t want to redesign/redevelop it or when your development team doesn t have much experience with other technologies. SQL Passthrough VFP provides a number of functions, sometimes referred to as SQL passthrough (or SPT) functions, which allow you to access a backend database. SQLCONNECT() and SQLSTRINGCONNECT() make a connection to the backend database engine; the difference between these two functions is that SQLCONNECT() requires an existing ODBC Data Source (DSN) that s been defined on the user s system, while SQLSTRINGCONNECT() simply passes the necessary information directly to ODBC, known as a DSNless connection. As you may guess, SQLDISCONNECT() disconnects from the backend. SQLEXEC() sends a command, such as a SQL SELECT statement, to the database engine, typically (but not necessarily, depending on the command) putting the returned results into a VFP cursor. Here s an example that opens a connection to the Northwind database (this assumes there s a DSN called Northwind that defines how to connect to this database), retrieves a single customer record and displays it in a BROWSE window, and then closes the connection. lnhandle = sqlconnect('northwind') if lnhandle > 0 sqlexec(lnhandle, "select * from customers where customerid = 'ALFKI'") browse sqldisconnect(lnhandle) else aerror(laerrors) messagebox('could not connect: ' + laerrors[2]) endif lnhandle > 0 To use a DSNless connection instead, replace the SQLCONNECT() statement with the following (replace the name of the server, user ID, and password with the appropriate values):

6 lnhandle = sqlstringconnect('driver=sql Server;Server=(local);' + ; Database=Northwind;uid=sa;pwd=whatever') The DispLogin SQL setting controls whether or not ODBC displays a login dialog. Although you might think it s handy to let ODBC worry about user names and passwords, you don t have any control over the appearance of the dialog or what happens if the user enters improper values. Instead, you re better off asking the user for the appropriate information in your own VFP dialog and then passing the information to ODBC. As described in the VFP help for the SQLCONNECT() function, you should use SQLSETPROP() to set DispLogin to 3. Rather than having each component that requires access to remote data manage their own ODBC connections, use an object whose sole responsibility is managing the connection and accessing the data. SFConnectionMgr, which is based on Custom, is an example. It has several custom properties, some of which you must set before you can use it to connect to a remote data source. Property cdatabase cdriver cdsn cerrormessage cpassword cserver cusername lconnected Description The database to connect to; can leave blank if cdsn filled in. The ODBC driver or OLE DB provider to use; can leave blank if cdsn filled in. The ODBC DSN to connect to; leave blank to use a DSNless connection. The message of any error that occurs. The password for the data source; can leave blank if a trusted connection is used. The server the database is on; can leave blank if cdsn filled in. The user name for the data source; can leave blank if a trusted connection is used..t. if we are connected to the data source. It has several custom methods: Method Connect Description Connects to the data source.

7 Disconnect Execute GetConnection GetConnectionString HandleError Disconnects from the data source (called from Destroy but can also call manually). Executes a statement against the data source. Returns the connection handle or ADO Connection object. Returns the connection string from the various connection properties (protected). Sets the cerrormessage property when an error occurs (protected). SFConnectionMgr isn t intended to be used directly, but is subclassed into SFConnectionMgrODBC and SFConnectionMgrADO, which are specific for ODBC and ADO, respectively. Those subclasses override most of the methods to provide the specific behavior needed. Let s look at SFConnectionMgrODBC. The Init method saves the current DispLogin setting to a custom ndisplogin property and sets it to 3 so the login dialog is never displayed. This.nDispLogin = sqlgetprop(0, 'DispLogin') sqlsetprop(0, 'DispLogin', 3) dodefault() The Connect method checks to see if we re already connected, then either uses SQLCONNECT() if a DSN was specified in the cdsn property or builds a connection string and uses the SQLSTRINGCONNECT() function. Either way, if the connection succeeded, the nhandle property contains the connection handle and lconnected is.t. If it failed, nhandle is 0, lconnected is.f, and cerrormessage contains information about what went wrong (set in the HandleError method, which we won t look at). * If we're not already connected, connect to either the specified DSN or to * the data source using a DSNless connection. local lcconnstring with This if not.lconnected if empty(.cdsn) lcconnstring =.GetConnectionString().nHandle = sqlstringconnect(lcconnstring) else.nhandle = sqlconnect(.cdsn,.cusername,.cpassword) endif empty(.cdsn) * Set the lconnected flag if we succeeded in connecting, and if not, get the * error information..lconnected =.nhandle > 0

8 if not.lconnected.nhandle = 0.HandleError() endif not.lconnected endif not.lconnected endwith return This.lConnected GetConnectionString returns a connection string from the values of the connection properties. local lcspecifier, ; lcconnstring with This lcspecifier = iif(upper(.cdriver) = 'SQL SERVER', 'database=', ; 'dbq=') lcconnstring = 'driver=' +.cdriver + ';' + ; iif(empty(.cserver), '', 'server=' +.cserver + ';') + ; iif(empty(.cusername), '', 'uid=' +.cusername + ';') + ; iif(empty(.cpassword), '', 'pwd=' +.cpassword + ';') + ; iif(empty(.cdatabase), '', lcspecifier +.cdatabase + ';') + ; 'trusted_connection=' + iif(empty(.cusername), 'yes', 'no') endwith return lcconnstring The Execute method executes a statement (such as a SQL SELECT command) against the data source. It first calls Connect to ensure we have a connection, then uses the SQLEXEC() function to pass the statement to the data source. lparameters tcstatement, ; tccursor local lccursor, ; llreturn with This.Connect() if.lconnected lccursor = iif(vartype(tccursor) = 'C' and not empty(tccursor), ; tccursor, sys(2015)) llreturn = sqlexec(.nhandle, tcstatement, lccursor) >= 0 if not llreturn.handleerror() endif not llreturn endif.lconnected endwith return llreturn The Disconnect method disconnects from the data source and sets nhandle to 0 and lconnected to.f. with This if.lconnected sqldisconnect(.nhandle).nhandle = 0.lConnected =.F. endif.lconnected endwith

9 Destroy simply disconnects and restores the saved DispLogin setting. This.Disconnect() sqlsetprop(0, 'DispLogin', This.nDispLogin) dodefault() Here s an example (taken from TestConnMgr.prg) that shows how SFConnectionMgrODBC can be used. It first connects to the SQL Server Northwind database and grabs all the customer records, and then it connects to the Access Northwind database and again retrieves all the customer records. Of course, this example uses hard-coded connection information; a real application would likely store this information in a local table, an INI file, or the Windows Registry to make it more flexible. loconnmgr = newobject('sfconnectionmgrodbc', 'SFRemote') with loconnmgr * Connect to the SQL Server Northwind database and get customer records..cdriver = 'SQL Server'.cServer = '(local)'.cdatabase = 'Northwind'.cUserName = 'sa'.cpassword = '' do case case not.connect() messagebox(.cerrormessage) case.execute('select * from customers') browse use otherwise messagebox(.cerrormessage) endcase * Now connect to the Access Northwind database and get customer records..disconnect().cdriver = 'Microsoft Access Driver (*.mdb)'.cserver = ''.cdatabase = 'd:\program Files\Microsoft Visual Studio\VB98\Nwind.mdb'.cUserName = ''.cpassword = '' do case case not.connect() messagebox(.cerrormessage) case.execute('select * from customers') browse use otherwise messagebox(.cerrormessage) endcase endwith

10 Advantages The advantages of using SPT are: You have a lot more flexibility in data access than with remote views, such as calling stored procedures using the SQLEXEC() function. You can change the connection information on the fly as needed. For example, you can store the user name and password as encrypted values and only decrypt them just before using them in the SQLCONNECT() or SQLSTRINGCONNECT() functions. You can also change servers or even backend database engines (for example, you could switch between SQL Server and Access database by simply changing the ODBC driver specified in the SQLSTRINGCONNECT() call). As I mentioned earlier, this isn t nearly the advantage over remote views that it used to be, now that VFP 7 allows you to specify the connection string on the USE command. You can change the SQL SELECT statement as needed. For example, you can easily vary the list of the fields, the WHERE clause (such as changing which fields are involved or eliminating it altogether), the tables, and so on. You don t need a DBC to use SPT, so there s nothing to maintain or install, lock contention isn t an issue, and you don t have to worry about a SELECT * statement being made invalid when the structure of the backend tables change. As with remote views, the result set of a SPT call is a VFP cursor, which can be used anywhere in VFP without the conversion issues that ADO and XML have. Although you have to code for it yourself (this is discussed in more detail under Disadvantages), you have greater control over how updates are done. For example, you might use a SQL SELECT statement to create the cursor but call a stored procedure to update the backend tables. You can manage your own connections. For example, you might want to use a connection manager similar to the one discussed earlier to manage all the connections used by your application in one place. Disadvantages The disadvantages of using SPT are: It s more work, since you have to code everything: creating and closing the connection, the SQL SELECT statements to execute, and so on. You don t have a nice visual tool like the View Designer to show you which fields exist in which tables on the backend. You can t visually add a cursor created by SPT to the DataEnvironment of a form or report. Instead, you have to code the opening of the cursors (for example, in the BeforeOpenTables method), you have to manually create the controls, and you have to fill in the binding

11 properties (such as ControlSource) by typing them yourself (don t make a typo when you enter the alias and field names or the form won t work). They re harder to use than remote views in a development environment: instead of just issuing a USE command, you have to create a connection, then use a SQLEXEC() call to get the data you want to look at. You can make things easier on yourself if you create a set of PRGs to do the work for you (such as UseCustomers.prg, which opens and displays the contents of the Customers table). You can also use the SQL Server Enterprise Manager (or similar tool) to examine the structures and contents of the tables. You could even create a DBC and set of remote views used only in the development environment as a quick way to look at the data. As with remote views and other types of VFP cursors, you can t pass the result set outside the current data session. Instead, you may have to pass the information used to create the cursor (the SQL SELECT statement or stored procedure call, and even possibly the connection information) and let the other object do the work itself. Cursors created with SPT can be updatable, but you have to make them so yourself using a series of CURSORSETPROP() calls to the SendUpdates, Tables, KeyFieldList, UpdatableFieldList, and UpdateNameList properties. Also, you have to manage transaction processing and update conflict detection yourself. Since SPT cursors aren t defined like remote views, you can t easily switch between local and remote data using SPT as you can with remote views (by simply changing which view you open in a form or report). Like remote views, SPT is based on ODBC only, so you can t take advantage of the benefits of ADO or XML. When to Use As with remote views, SPT is best suited to client-server, 2-tier applications where you have a direct connection to the data. Since it s more work to convert an existing application to SPT than remote view or CursorAdapters (which we ll see later), SPT is best suited to utilities, simple applications, or narrow-focus cases. ADO OLE DB and ADO are part of Microsoft s Universal Data Access strategy, in which data of any type stored anywhere in any format, not just in relational databases on a local server, can be made available to any application requiring it. OLE DB providers are similar to ODBC drivers: they provide a standard, consistent way to access data sources. A variety of OLE DB providers are available for specific DBMS (SQL Server, Oracle, Access/Jet, etc.), and Microsoft provides an OLE DB provider for ODBC data sources.

12 Because OLE DB is a set of low-level COM interfaces, it s not easy to work with in languages like VFP. To overcome this, Microsoft created ActiveX Data Objects, or ADO, a set of COM objects that provide an object-oriented front-end to OLE DB. ADO consists of several objects, including: Connection: This is the object responsible for communicating with the data source. RecordSet: This is the equivalent of a VFP cursor: it has a defined structure, contains the data in the data set, and provides properties and methods to add, remove, or update records, move from one to another, filter or sort the data, and update the data source. Command: This object provides the means of doing more advanced queries than a simple SELECT statement, such as parameterized queries and calling stored procedures. Here s an example (ADOExample.prg) that gets all Brazilian customers from the Northwind database and displays the customer ID and company name. Notice that the Connection object handles the connection (the RecordSet s ActiveConnection property is set to the Connection object), while the RecordSet handles the data. local loconn as ADODB.Connection, ; lors as ADODB.Recordset loconn = createobject('adodb.connection') loconn.connectionstring = 'provider=sqloledb.1;data source=(local);' + ; 'initial catalog=northwind;uid=sa;pwd=' loconn.open() lors = createobject('adodb.recordset') lors.activeconnection = loconn lors.locktype = 3 && adlockoptimistic lors.cursorlocation = 3 && aduseclient lors.cursortype = 3 && adopenstatic lors.open("select * from customers where country='brazil'") lccustomers = '' do while not lors.eof lccustomers = lccustomers + lors.fields('customerid').value + chr(9) + ; lors.fields('companyname').value + chr(13) lors.movenext() enddo while not lors.eof messagebox(lccustomers) lors.close() loconn.close() Because of its object-oriented nature and its capabilities, ADO has been the data access mechanism of choice for n-tier development (although this is quickly changing as XML becomes more popular). Unlike ODBC, you don t have to have a direct connection to the data source. For details on ADO and using it in VFP, see John Petersen s ADO Jumpstart for Visual FoxPro Developers white paper, available from the VFP home page ( follow the Technical Resources, Technical Articles, and COM and ActiveX Development links to get to the document).

13 Advantages The advantages of using ADO are: As with SPT, you have more flexibility in data access than with remote views, such as calling stored procedures. You can change the connection information on the fly as needed, such as encrypting the user name and password, changing servers, or even backend database engines. You can change the SQL SELECT statement as needed. There s no DBC involved. Although performance differences aren t significant in simple scenarios (in fact, in my testing, ODBC is faster than ADO), ADO is more scalable in heavily-used applications such as Web servers. Unlike VFP cursors, ADO objects can be passed outside the current data session, whether to another component in the application, to another application or COM object (such as Excel or a middle-tier component), or to another computer altogether. You can even send them over HTTP using Remote Data Services (RDS; see John Petersen s white paper for more information), although this is a problem when firewalls are involved. ADO is object-oriented, so you can deal with the data like objects. Depending on how it s set up, ADO RecordSets are automatically updateable without any additional work (other than calling the Update or UpdateBatch methods). Transaction processing and update conflict detection are built-in. You can manage your own connections. You can easily persist a RecordSet to a local file, then later reload it and carry on working, and finally update the backend data source. This makes it a much better choice for road warrior applications than remote views or SPT. Disadvantages The disadvantages of ADO are: It s more work, since you have to code everything: creating and closing the connection, the SQL SELECT statements to execute, and so on. You don t have a nice visual tool like the View Designer to show you which fields exist in which tables on the backend. An ADO RecordSet is not a VFP cursor, so you can t use it in places that require a cursor, such as grids and reports. There are functions in the VFPCOM utility (available for download from the VFP home page, that can convert a RecordSet

14 to a cursor and vice versa, but using them can impact performance, especially with large data sets, and they have known issues with certain data types. There s no visual support for ADO RecordSets, so have to code their creation and opening, you have to manually create the controls, and you have to fill in the binding properties (such as ControlSource) by typing them yourself. This is even more work than for SPT, because the syntax isn t just CURSOR.FIELD it s RecordSet.Fields('FieldName').Value. They re the hardest of the technologies to use in a development environment, since you have to code everything: making a connection, retrieving the data, moving back and forth between records. You can t even BROWSE to see visually what the result set looks like (unless you use VFPCOM or another means to convert the RecordSet to a cursor). There s a bigger learning curve involved with ADO than using the cursors created by ODBC. You ll likely have to ensure the client has the latest version of Microsoft Data Access Components (MDAC) installed to ensure their version of OLEDB and ADO match what your application requires. ADO is a Windows-only technology. When to Use ADO is easily used when passing data back and forth between other components. For example, a method of a VFP COM object can easily return an ADO RecordSet to Excel VBA code, which can then process and display the results. If you re designing an application using an n-tier architecture, ADO may be a good choice if you re already familiar with it or already have infrastructure in place for it. However, XML is fast becoming the mechanism of choice for n-tier applications, so I expect ADO to be used less and less in this area. XML XML (Extendible Markup Language) isn t really a data access mechanism; it s really a transport technology. The data is packaged up as text with a structured format and then shipped off somewhere. However, because it s simply text, XML has a lot of advantages over other technologies (as we ll discuss later). A few years ago, Microsoft discovered XML, and has been implementing it pretty much everywhere since then. The data access technology built into the.net framework, ADO.NET, has XML as its basis (in fact, one simplistic view is that ADO.NET is really just a set of wrapper classes that expose XML data via an OOP interface). Web Services, based on SOAP (Simple Object Access Protocol), use XML as the basis for communications and data transfer. XML is even quickly becoming the data transport mechanism of choice for n-tier applications, which for a long time favored ADO in that role.

15 VFP 7 added several functions that work with XML: XMLTOCURSOR(), which converts XML to a cursor, CURSORTOXML(), which does the opposite, and XMLUPDATEGRAM(), which generates an updategram (XML in a specific format for indicating changes to data) from changes to a cursor. Here s some VFP code (taken from XMLExample1.prg) that shows how VFP can deal with XML: * Get the information for the ALFKI customer and display the raw XML. close databases all lcxml = GetCustomerByID('ALFKI') strtofile(lcxml, 'ALFKI.XML') modify file ALFKI.XML erase ALFKI.XML * Put it into a cursor, browse and make changes, then view the updategram. xmltocursor(lcxml, 'CUSTOMERS') set multilocks on cursorsetprop('buffering', 5) browse lcupdate = xmlupdategram() strtofile(lcupdate, 'UPDATE.XML') modify file UPDATE.XML * By setting the KeyFieldList property, the updategram will only contain key * and changed fields. cursorsetprop('keyfieldlist', 'cust_id') lcupdate = xmlupdategram() strtofile(lcupdate, 'UPDATE.XML') modify file UPDATE.XML erase UPDATE.XML close databases all * This function returns the specified customer record as XML. function GetCustomerByID(tcCustomerID) local lcxml open database (_samples + 'data\testdata') select * from customer where cust_id = tccustomerid into cursor Temp cursortoxml('temp', 'lcxml', 1, 8, 0, '1') use in Temp use in Customer return lcxml Note that until version 8, while VFP could create an XML updategram, it didn t have a simple way to consume one (that is, to update VFP tables). Visual FoxPro MVP Alex Feldstein wrote a routine to do this ( but in VFP 8, you can use an instance of the new XMLAdapter class to do this (we ll take a look at that class in the Data Strategies in VFP: Advanced document. Here s an example (XMLExample2.prg) that uses SQLXML to get a customer s record from SQL Server via a Web Server (we ll discuss SQLXML in more detail in the Advanced document), then send changes back.

16 * Get the information for the ALFKI customer and display the raw XML. close databases all lcxml = GetNWCustomerByID('ALFKI') strtofile(lcxml, 'ALFKI.XML') modify file ALFKI.XML erase ALFKI.XML * Put it into a cursor, browse, and make changes. xmltocursor(lcxml, 'CUSTOMERS') cursorsetprop('keyfieldlist', 'customerid') set multilocks on cursorsetprop('buffering', 5) browse * Get the updategram and save the changes to SQL Server. lcupdate = xmlupdategram() SaveNWCustomers(lcUpdate) use * This function uses SQLXML to get the specified customer record as XML. function GetNWCustomerByID(tcCustomerID) local loxml as MSXML2.XMLHTTP loxml = createobject('msxml2.xmlhttp') loxml.open('post', ' ' + ; 'customersbyid.xml?customerid=' + tccustomerid,.f.) loxml.setrequestheader('content-type', 'text/xml') loxml.send() return loxml.responsetext * This function uses SQLXML to get the specified customer record as XML. function SaveNWCustomers(tcDiffGram) local lodom as MSXML2.DOMDocument, ; loxml as MSXML2.XMLHTTP lodom = createobject('msxml2.domdocument') lodom.async =.F. lodom.loadxml(tcdiffgram) loxml = createobject('msxml2.xmlhttp') loxml.open('post', ' loxml.setrequestheader('content-type', 'text/xml') loxml.send(lodom) Advantages There are a lot of benefits to using XML: You have the same benefits over remote views, such as no DBC and an easily-changed SQL SELECT statement, as SPT and ADO have. Since XML isn t really a data access mechanism, you have the most flexibility in data access with XML. For example, you can use the VFP CURSORTOXML() function, Web Services,

17 middle tier components, ADO.NET DataSets, XMLHTTP, and many other mechanisms to get data from one place to another. Since it s just text, XML can be passed anywhere, even through firewalls (which is a problem for ADO). The XML DOM object provides an object-oriented interface to the XML data. XML is easily persisted anywhere: to a file, a memo field in a table, etc. XML is completely platform/operating system-independent. XML is human-readable, unlike the binary formats of other mechanisms. Disadvantages There are also some downsides to using XML: Belying its simple format, there s a bigger learning curve with XML than there is with the other mechanisms. The XML DOM object has its own object model, there s all kinds of new technology (and acronyms!) to learn like schemas, XSLT, XPath, XDR, and XQueries. The same set of data can be quite a bit larger in XML than in the other mechanisms because every element has beginning and ending tags. For example, the VFP CUSTOMER sample table goes from 26,257 bytes as a DBF file to 40,586 as XML. While CURSORTOXML() is fast, XMLTOCURSOR(), which uses the XML DOM object to do the work, can be quite slow, especially with large amounts of data. XML standards are still evolving. When to Use XML is great for lots of things, including storing configuration settings, passing small amounts of data between application components, storing structured data in memo fields, etc. However, XML is ideally suited to n-tier applications because it s easily transported (either between components or across a wire) and converted to and from data sets such as VFP cursors. Using XML updategrams (and the newer diffgrams), you can limit the amount of data being transported. If you re starting new n-tier projects, this is clearly the data access mechanism to use. CursorAdapter One of the things you ve likely noted is that each of the mechanisms we ve looked at is totally different from the others. That means you have a new learning curve with each one, and converting an existing application from one mechanism to another is a non-trivial task.

18 In my opinion, CursorAdapter is one of the biggest new features in VFP 8. The reasons I think they re so cool are: They make it easy to use ODBC, ADO, or XML, even if you re not very familiar with these technologies. They provide a consistent interface to remote data regardless of the mechanism you choose. They make it easy to switch from one mechanism to another. Here s an example of the last point. Suppose you have an application that uses ODBC with CursorAdapters to access SQL Server data, and for some reason you want to change to use ADO instead. All you need to do is change the DataSourceType of the CursorAdapters and change the connection to the backend database, and you re done. The rest of the components in the application neither know nor care about this; they still see the same cursor regardless of the mechanism used to access the data. We ll take a close look at CursorAdapter in the Advanced document. However, in the meantime, here s an example (CursorAdapterExample.prg) that gets certain fields for Brazilian customers from the Customers table in the Northwind database. The cursor is updateable, so if you make changes in the cursor, close it, then run the program again, you ll see that your changes were saved to the backend. local locursor as CursorAdapter, ; laerrors[1] locursor = createobject('cursoradapter') with locursor.alias = 'Customers'.DataSourceType = 'ODBC'.DataSource = sqlstringconnect('driver=sql Server;' + ; 'server=(local);database=northwind;uid=sa;pwd=;trusted_connection=no').selectcmd = "select CUSTOMERID, COMPANYNAME, CONTACTNAME " + ; "from CUSTOMERS where COUNTRY = 'Brazil'".KeyFieldList = 'CUSTOMERID'.Tables = 'CUSTOMERS'.UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME'.UpdateNameList = 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ; 'COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME' if.cursorfill() browse else aerror(laerrors) messagebox(laerrors[2]) endif.cursorfill() endwith Advantages The advantages of CursorAdapters are essentially the combination of those of all of the other technologies.

19 Depending on how it s set up (if it s completely self-contained, for example), opening a cursor from a CursorAdapter subclass can almost be as easy as opening a remote view: you simply instantiate the subclass and call the CursorFill method (you could even call that from Init to make it a single-step operation). It s easier to convert an existing application to use CursorAdapters than to use cursors created with SPT. Like remote views, you can add a CursorAdapter to the DataEnvironment of a form or report and take advantage of the visual support the DE provides: dragging and dropping fields to automatically create controls, easily binding a control to a field by selecting it from a combobox in the Properties Window, and so on. It s easy to update the backend with changes: assuming the properties of the view have been set up properly, you simply call TABLEUPDATE(). Because the result set created by a CursorAdapter is a VFP cursor, they can be used anywhere in VFP: in a grid, a report, processed in a SCAN loop, and so forth. This is true even if the data source comes from ADO and XML, because the CursorAdapter automatically takes care of conversion to and from a cursor for you. You have a lot of flexibility in data access, such as calling stored procedures or middle-tier objects. You can change the connection information on the fly as needed. You can change the SQL SELECT statement as needed. You don t need a DBC. They can work with ODBC, ADO, XML, or native tables, allowing you to take advantage of the benefits of any of these technologies or even switch technologies as required. Although you have to code for it yourself (this is discussed in more detail under Disadvantages), you have greater control over how updates are done. For example, you might use a SQL SELECT statement to create the cursor but call a stored procedure to update the backend tables. You can manage your own connections. Disadvantages There aren t a lot of disadvantages for CursorAdapters: You can t use a nice visual tool like the View Designer to create CursorAdapters, although the CursorAdapter Builder is a close second.

20 Like other types of VFP cursors, you can t pass the result set outside the current data session. However, since CursorAdapters are really for the UI layer, that isn t much of an issue. They re awkward to use in reports; we ll discuss this in more detail in the Advanced document. Like all new technologies, there s a learning curve that must be mastered. When to Use Because CursorAdapters create VFP cursors, you won t likely do much with them in the middle tier of an n-tier application. However, in the UI layer, I see CursorAdapters replacing other techniques for all remote data access and even replacing Cursors in new applications that might one day be upsized. Summary This document discussed the advantages and disadvantages of ODBC (whether remote views or SQL passthrough), ADO, and XML as means of accessing non-vfp data such as SQL Server or Oracle. As usual, which mechanism you should choose for a particular application depends on many factors. However, using the new CursorAdapter technology in VFP 8 makes the transition to remote data access easier, and makes it easier to switch between mechanisms should that need arise. In the Data Strategies in VFP: Advanced document, we ll discuss the CursorAdapter class in detail, looking at specifics of accessing native data or non-vfp data using ODBC, ADO, and XML. We ll also look at creating reusable data classes, and discuss how to use CursorAdapters in reports. Biography Doug Hennig is a partner with Stonefield Systems Group Inc. He is the author of the awardwinning Stonefield Database Toolkit (SDT) and co-author of the award-winning Stonefield Query. He is co-author (along with Tamar Granor, Ted Roche, and Della Martin) of "The Hacker's Guide to Visual FoxPro 7.0" and co-author (along with Tamar Granor and Kevin McNeish) of "What's New in Visual FoxPro 7.0", both from Hentzenwerke Publishing, and author of "The Visual FoxPro Data Dictionary" in Pinnacle Publishing's Pros Talk Visual FoxPro series. He writes the monthly "Reusable Tools" column in FoxTalk. He was the technical editor of "The Hacker's Guide to Visual FoxPro 6.0" and "The Fundamentals", both from Hentzenwerke Publishing. Doug has spoken at every Microsoft FoxPro Developers Conference (DevCon) since 1997 and at user groups and developer conferences all over North America. He is a Microsoft Most Valuable Professional (MVP) and Certified Professional (MCP).

21 Copyright 2002 Doug Hennig. All Rights Reserved Doug Hennig Partner Stonefield Systems Group Inc Winnipeg Street, Suite 200 Regina, SK Canada S4R 1J6 Phone: (306) Fax: (306) Web:

22 Data Strategies in VFP: Advanced By Doug Hennig Introduction In the Data Strategies in VFP: Introduction document, we examined the different mechanisms for accessing non-vfp data (such as SQL Server) in VFP applications: remote views, SQL passthrough, ADO, XML, and the CursorAdapter class added in VFP 8. In this document, we ll look at CursorAdapter in a lot more detail and discuss the concept of reusable data classes. In addition, we ll take a brief look at the new XMLAdapter base class and see how it can help exchange data with other sources, such as ADO.NET. CursorAdapter In my opinion, CursorAdapter is one of the biggest new features in VFP 8. The reasons I think they re so cool are: They make it easy to use ODBC, ADO, or XML, even if you re not very familiar with these technologies. They provide a consistent interface to remote data regardless of the mechanism you choose. They make it easy to switch from one mechanism to another. Here s an example of the last point. Suppose you have an application that uses ODBC with CursorAdapters to access SQL Server data, and for some reason you want to change to use ADO instead. All you need to do is change the DataSourceType of the CursorAdapters and change the connection to the backend database, and you re done. The rest of the components in the application neither know nor care about this; they still see the same cursor regardless of the mechanism used to access the data. Let s start examining CursorAdapters by looking at their properties, events, and methods (PEMs). PEMS We won t discuss all of the properties, events, and methods of the CursorAdapter class here, just the more important ones. See the VFP documentation for the complete list. DataSourceType This property is a biggie: it determines the behavior of the class and what kinds of values to put into some of the other properties. The valid choices are Native, which indicates that you re using native tables, or ODBC, ADO, or XML, which means you re using the appropriate mechanism to access the data. You likely won t use Native, since you would probably use a

23 Cursor object rather than CursorAdapter, but this setting would make it easier to later upsize an application. DataSource This is the means to access the data. VFP ignores this property when DataSourceType is set to Native or XML. For ODBC, set DataSource to a valid ODBC connection handle (meaning you have to manage the connection yourself). In the case of ADO, DataSource must be an ADO RecordSet that has its ActiveConnection object set to an open ADO Connection object (again, you have to manage this yourself). UseDEDataSource If this property is set to.t. (the default is.f.), you can leave the DataSourceType and DataSource properties alone since the CursorAdapter will use the DataEnvironment s properties instead (VFP 8 adds DataSourceType and DataSource to the DataEnvironment class as well). An example of when you d set this to.t. is when you want all the CursorAdapters in a DataEnvironment to use the same ODBC connection. SelectCmd In the case of everything but XML, this is the SQL SELECT command used to retrieve the data. In the case of XML, this can either be a valid XML string that can be converted into a cursor (using an internal XMLTOCURSOR() call) or an expression (such as a UDF) that returns a valid XML string. CursorSchema This property holds the structure of the cursor in the same format as you d use in a CREATE CURSOR command (everything between the parentheses in such a command). Here s an example: CUST_ID C(6), COMPANY C(30), CONTACT C(30), CITY C(25). Although it s possible to leave this blank and tell the CursorAdapter to determine the structure when it creates the cursor, it works better if you fill CursorSchema in. For one thing, if CursorSchema is blank or incorrect, you ll either get errors when you open the DataEnvironment of a form or you won t be able to drag and drop fields from the CursorAdapter to the form to create controls. Fortunately, the CursorAdapter Builder that comes with VFP can automatically fill this in for you. AllowDelete, AllowInsert, AllowUpdate, and SendUpdates These properties, which default to.t., determine if deletes, inserts, and updates can be done and if changes are sent to the data source. KeyFieldList, Tables, UpdatableFieldList, and UpdateNameList These properties, which serve the same purpose as the same-named CURSORSETPROP() properties for views, are required if you want VFP to automatically update the data source with changes made in the cursor. KeyFieldList is a comma-delimited list of fields (without aliases)

24 that make up the primary key for the cursor. Tables is a comma-delimited list of tables. UpdatableFieldList is a comma-delimited list of fields (without aliases) that can be updated. UpdateNameList is a comma-delimited list that matches field names in the cursor to field names in the table. The format for UpdateNameList is as follows: CURSORFIELDNAME1 TABLE.FIELDNAME1, CURSORFIELDNAME2 TABLE.FIELDNAME2,... Note that even if UpdatableFieldList doesn t contain the name of the primary key of the table (because you don t want that field updated), it must still exist in UpdateNameList or updates won t work. *Cmd, *CmdDataSource, *CmdDataSourceType If you want to specifically control how VFP deletes, inserts, or updates records in the data source, you can assign the appropriate values to these sets of properties (replace the * above with Delete, Insert, and Update). CursorFill(UseCursorSchema, NoData, Options, Source) This method creates the cursor and fills it with data from the data source (although you can pass.t. for the NoData parameter to create an empty cursor). Pass.T. for the first parameter to use the schema defined in CursorSchema or.f. to create an appropriate structure from the data source (in my opinion, this behavior is reversed). MULTILOCKS must be set on or this method will fail. If CursorFill fails for any reason, it returns.f. rather than raising an error; use AERROR() to determine what went wrong (although be prepared for some digging, since the error messages you get often aren t specific enough to tell you exactly what the problem is). CursorRefresh() This method is similar to the REQUERY() function: it refreshes the cursor s contents. Before*() and After*() Pretty much every method and event has before and after hook events that allow you to customize the behavior of the CursorAdapter. For example, in AfterCursorFill, you can create indexes for the cursor so they re always available. In the case of the Before events, you can return.f. to prevent the action that trigger it from occurring (this is similar to database events). Here s an example (CursorAdapterExample.prg) that gets certain fields for Brazilian customers from the Customers table in the Northwind database that comes with SQL Server. The cursor is updateable, so if you make changes in the cursor, close it, and then run the program again, you ll see that your changes were saved to the backend. local locursor as CursorAdapter, ; laerrors[1] locursor = createobject('cursoradapter') with locursor.alias = 'Customers'.DataSourceType = 'ODBC'.DataSource = sqlstringconnect('driver=sql Server;' + ; 'server=(local);database=northwind;uid=sa;pwd=;trusted_connection=no').selectcmd = "select CUSTOMERID, COMPANYNAME, CONTACTNAME " + ;

25 "from CUSTOMERS where COUNTRY = 'Brazil'".KeyFieldList = 'CUSTOMERID'.Tables = 'CUSTOMERS'.UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME'.UpdateNameList = 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ; 'COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME' if.cursorfill() browse else aerror(laerrors) messagebox(laerrors[2]) endif.cursorfill() endwith DataEnvironment and Form Changes To support the new CursorAdapter class, several changes have been made to the DataEnvironment and Form classes and their designers. First, as I mentioned earlier, the DataEnvironment class now has DataSource and DataSourceType properties. It doesn t use these properties itself but they re used by any CursorAdapter member that has UseDEDataSource set to.t. Second, you can now create DataEnvironment subclasses visually using the Class Designer (woo hoo!). As for forms, you can now specify a DataEnvironment subclass to use by setting the new DEClass and DEClassLibrary properties. If you do this, anything you ve done with the existing DataEnvironment (cursors, code, etc.) will be lost, but at least you re warned first. A cool new feature of forms that s somewhat related is the BindControls property; setting this to.f. in the Property Window means that VFP won t try to data bind the controls at init time, only when you set BindControls to.t. What s this good for? Well, how many times have you cursed that parameters are passed to Init, which fires after all the controls have been initialized and bound to their ControlSources? What if you want to pass a parameter to a form that tells it which table to open or other things that affect the ControlSources? This new property makes this issue a snap. Other Changes CURSORGETPROP('SourceType') returns a new range of values: if the cursor was created with CursorFill, the value is the old value (for example, 102 for remote data). If the cursor was created with CursorAttach (which allows you to attach an existing cursor to a CursorAdapter object), the value is the old value. If the data source is an ADO RecordSet, the value is 104 (CursorFill) or 204 (CursorAttach). Builders VFP includes DataEnvironment and CursorAdapter builders that make it easier to work with these classes. The DataEnvironment Builder is brought up in the usual way: by right-clicking on the DataEnvironment of a form or on a DataEnvironment subclass in the Class Designer and

26 choosing Builder. The Data Source page of the DataEnvironment Builder is where you set data source information. Choose the desired data source type and where the data source comes from. If you choose Use existing connection handle (ODBC) or Use existing ADO RecordSet (ADO), specify the expression containing the data source (such as goconnectionmgr.nhandle ). You can also choose to use one of the DSNs on your system or a connection string. The Build button, which is only enabled if you choose Use connection string for ADO, displays the Data Link Properties dialog, which you can use to build the connection string visually. If you select either Use DSN or Use connection string, the builder will generate code in the BeforeOpenTables method of the DataEnvironment to create the desired connection. If you choose Native, you can select a VFP database container as a data source; in that case, the generated code will ensure the database is open (you can also use free tables as the data source).

27 The Cursors page allows you to maintain the CursorAdapter members of the DataEnvironment (Cursor objects don t show up, nor can they be added, in the builder). The Add button allows you to add a CursorAdapter subclass to the DataEnvironment, while New create a new base class CursorAdapter. Remove deletes the select CursorAdapter and Builder invokes the CursorAdapter Builder for the selected CursorAdapter. You can change the name of the CursorAdapter object, but you ll need the CursorAdapter Builder for any other properties.

28 The CursorAdapter Builder is also invoked by choosing Builder from the shortcut menu. The Properties page shows the class and name of the object (Name can only be changed if the builder is brought up from a DataEnvironment, since it s read-only for a CursorAdapter subclass), the alias of the cursor it ll create, whether the DataEnvironment s data source should be used or not, and connection information if not. As with the DataEnvironment Builder, the CursorAdapter Builder will generate code to create the desired connection (in the CursorFill method in this case) if you select either Use DSN or Use connection string.

29 The Data Access page allows you to specify the SelectCmd, CursorSchema, and other properties. If you have specified connection information, you can click on the Build button for SelectCmd to display the Select Command Builder, which makes it easy to create the SelectCmd. The Select Command Builder makes short work of building a simple SELECT statement. Choose the desired table from the table dropdown, then move the appropriate fields to the selected side. In the case of a native data source, you can add tables to the Table combobox (such as if you want use free tables). When you choose OK, the SelectCmd will be filled with the appropriate SQL SELECT statement. Click on the Build button for the CursorSchema to have this property filled in for you automatically. In order for this to work, the builder actually creates a new CursorAdapter object, sets the properties appropriately, and calls CursorFill to create the cursor. If you don t have a live connection to the data source or CursorFill fails for some reason (such as an invalid SelectCmd), this obviously won t work.

30 Use the Auto-Update page to set the properties necessary for VFP to automatically generate update statements for the data source. The Tables property is automatically filled in from the tables specified in SelectCmd, and the fields grid is filled in from the fields in CursorSchema. Like the View Designer, you select which are the key fields and which fields are updatable by checking the appropriate column in the grid. You can also set other properties, such as functions to convert the data in certain fields of the cursor before sending it to the data source.

31 The Update, Insert, and Delete pages have a nearly identical appearance. They allow you to specify values for the sets of Update, Delete, and Insert properties. This is especially important for XML, for which VFP can t automatically generate update statements. Using Native Data Even though it s clear that CursorAdapter was intended to standardize and simplify the access to non-vfp data, you can use it as a substitute for Cursor by setting DataSourceType to Native. Why would you do this? Mostly as a look toward the future when your application might be upsized; by simply changing the DataSourceType to one of the other choices (and likely changing a few other properties such as setting connection information), you can easily switch to another DBMS such as SQL Server. When DataSourceType is set to Native, VFP ignores DataSource. SelectCmd must be a SQL SELECT statement, not a USE command or expression, so that means you re always working with the equivalent of a local view rather than the table directly. You re responsible for ensuring that VFP can find any tables referenced in the SELECT statement, so if the tables aren t in the current directory, you either need to set a path or open the database the tables belong to. As usual, if you want the cursor to be updateable, be sure to set the update properties (KeyFieldList, Tables, UpdatableFieldList, and UpdateNameList). The following example (NativeExample.prg) creates an updateable cursor from the Customer table in the TestData VFP sample database: local locursor as CursorAdapter, ; laerrors[1] open database (_samples + 'data\testdata')

32 locursor = createobject('cursoradapter') with locursor.alias = 'customercursor'.datasourcetype = 'Native'.SelectCmd = "select CUST_ID, COMPANY, CONTACT from CUSTOMER " + ; "where COUNTRY = 'Brazil'".KeyFieldList = 'CUST_ID'.Tables = 'CUSTOMER'.UpdatableFieldList = 'CUST_ID, COMPANY, CONTACT'.UpdateNameList = 'CUST_ID CUSTOMER.CUST_ID, ' + ; 'COMPANY CUSTOMER.COMPANY, CONTACT CUSTOMER.CONTACT' if.cursorfill() browse tableupdate(1) else aerror(laerrors) messagebox(laerrors[2]) endif.cursorfill() endwith close databases all Using ODBC ODBC is actually the most straightforward of the four settings of DataSourceType. You set DataSource to an open ODBC connection handle, set the usual properties, and call CursorFill to retrieve the data. If you fill in KeyFieldList, Tables, UpdatableFieldList, and UpdateNameList, VFP will automatically generate the appropriate UPDATE, INSERT, and DELETE statements to update the backend with any changes. If you want to use a stored procedure instead, set the *Cmd, *CmdDataSource, and *CmdDataSourceType properties appropriately. Here s an example, taken from ODBCExample.prg, that calls the CustOrderHist stored procedure in the Northwind database to get total units sold by product for a specific customer: local locursor as CursorAdapter, ; laerrors[1] locursor = createobject('cursoradapter') with locursor.alias = 'CustomerHistory'.DataSourceType = 'ODBC'.DataSource = sqlstringconnect('driver=sql Server;server=(local);' + ; 'database=northwind;uid=sa;pwd=;trusted_connection=no').selectcmd = "exec CustOrderHist 'ALFKI'" if.cursorfill() browse else aerror(laerrors) messagebox(laerrors[2]) endif.cursorfill() endwith

33 Using ADO Using ADO as the data access mechanism with CursorAdapter has a few more issues than using ODBC: DataSource must be set to an ADO RecordSet that has its ActiveConnection property set to an open ADO Connection object. If you want to use a parameterized query (which is likely the usual case rather than retrieving all records), you have to pass an ADO Command object that has its ActiveConnection property set to an open ADO Connection object as the fourth parameter to CursorFill. VFP will take care of filling the Parameters collection of the Command object (it parses SelectCmd to find the parameters) for you, but of course the variables containing the values of the parameters must be in scope. Using one CursorAdapter with ADO in a DataEnvironment is straightforward: you can set UseDEDataSource to.t. if you wish, then set the DataEnvironment s DataSource and DataSourceType properties as you would with the CursorAdapter. However, this doesn t work if there s more than one CursorAdapter in the DataEnvironment. The reason is that the ADO RecordSet referenced by DataEnvironment.DataSource can only contain a single CursorAdapter s data; when you call CursorFill for the second CursorAdapter, you get a RecordSet is already open error. So, if your DataEnvironment has more than one CursorAdapter, you must set UseDEDataSource to.f and manage the DataSource and DataSourceType properties of each CursorAdapter yourself (or perhaps use a DataEnvironment subclass that manages that for you). The sample code below, taken from ADOExample.prg, shows how to retrieve data using a parameterized query with the help of an ADO Command object. This example also shows the use of the new structured error handling features in VFP 8; the call to the ADO Connection Open method is wrapped in a TRY... CATCH... ENDTRY statement to trap the COM error the method will throw if it fails. local loconn as ADODB.Connection, ; locommand as ADODB.Command, ; loexception as Exception, ; locursor as CursorAdapter, ; lccountry, ; laerrors[1] loconn = createobject('adodb.connection') with loconn.connectionstring = 'provider=sqloledb.1;data source=(local);' + ; 'initial catalog=northwind;uid=sa;pwd=;trusted_connection=no' try.open() catch to loexception messagebox(loexception.message) cancel endtry endwith locommand = createobject('adodb.command')

34 locursor = createobject('cursoradapter') with locursor.alias = 'Customers'.DataSourceType = 'ADO'.DataSource = createobject('adodb.recordset').selectcmd = 'select * from customers where country=?lccountry' lccountry = 'Brazil'.DataSource.ActiveConnection = loconn locommand.activeconnection = loconn if.cursorfill(.f.,.f., 0, locommand) browse else aerror(laerrors) messagebox(laerrors[2]) endif.cursorfill(.f.,.f., 0, locommand) endwith Using XML Using XML with CursorAdapters requires some additional things. Here are the issues: The DataSource property is ignored. The CursorSchema property must be filled in, even if you pass.f. as the first parameter to the CursorFill method, or you ll get an error. The SelectCmd property must be set to an expression, such as a user-defined function (UDF) or object method name, that returns the XML for the cursor. Changes made to the cursor are converted to a diffgram, which is XML that contains before and after values for changed fields and records, and placed in the UpdateGram property when the update is required. In order to write changes back to the data source, UpdateCmdDataSourceType must be set to XML and UpdateCmd must be set to an expression (again, likely a UDF or object method) that handles the update. You ll probably want to pass This.UpdateGram to the UDF so it can send the changes to the data source. The XML source for the cursor could come from a variety of places. For example, you could call a UDF that converts a VFP cursor into XML using CURSORTOXML() and returns the results: use CUSTOMERS cursortoxml('customers', 'lcxml', 1, 8, 0, '1') return lcxml The UDF could call a Web Service that returns a result set as XML. Here s an example that IntelliSense generated for me from a Web Service I created and registered on my own system (the details aren t important; it just shows an example of a Web Service). local lows as dataserver web service lows = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx")

35 lows.cwsname = "dataserver web service" lows = lows.setupclient(" ; "dataserver", "dataserversoapport") lcxml = lows.getcustomers() return lcxml It could use SQLXML 3.0 to execute a SQL Server 2000 query stored in a template file on a Web Server (for more information on SQLXML, go to and search for SQLXML). The following code uses an MSXML2.XMLHTTP object to get all records from the Northwind Customers table via HTTP; this will be explained in more detail later. local loxml as MSXML2.XMLHTTP loxml = createobject('msxml2.xmlhttp') loxml.open('post', ' + ; 'getallcustomers.xml,.f.) loxml.setrequestheader('content-type', 'text/xml') loxml.send() return loxml.responsetext Handling updates is more complicated. The data source must either be capable of accepting and consuming a diffgram (as is the case with SQL Server 2000) or you ll have to figure out the changes yourself and issue a series of SQL statements (UPDATE, INSERT, and DELETE) to perform the updates. Here s an example (XMLExample.prg) that uses a CursorAdapter with an XML data source. Notice that both SelectCmd and UpdateCmd call UDFs. In the case of SelectCmd, the name of a SQL Server 2000 XML template and the customer ID to retrieve is passed to a UDF called GetNWXML, which we ll look at in a moment. For UpdateCmd, VFP passed the UpdateGram property to SendNWXML, which we ll also look at later. local locustomers as CursorAdapter, ; laerrors[1] locustomers = createobject('cursoradapter') with locustomers.alias = 'Customers'.CursorSchema = 'CUSTOMERID C(5), COMPANYNAME C(40), ' + ; 'CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60), ' + ; 'CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), ' + ; 'PHONE C(24), FAX C(24)'.DataSourceType = 'XML'.KeyFieldList = 'CUSTOMERID'.SelectCmd = 'GetNWXML([customersbyid.xml?customerid=ALFKI])'.Tables = 'CUSTOMERS'.UpdatableFieldList = 'CUSTOMERID, COMPANYNAME, CONTACTNAME, ' + ; 'CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX'.UpdateCmdDataSourceType = 'XML'.UpdateCmd = 'SendNWXML(This.UpdateGram)'.UpdateNameList = 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ; 'COMPANYNAME CUSTOMERS.COMPANYNAME, ' + ; 'CONTACTNAME CUSTOMERS.CONTACTNAME, ' + ; 'CONTACTTITLE CUSTOMERS.CONTACTTITLE, ' + ; 'ADDRESS CUSTOMERS.ADDRESS, ' + ; 'CITY CUSTOMERS.CITY, ' + ; 'REGION CUSTOMERS.REGION, ' + ;

36 'POSTALCODE CUSTOMERS.POSTALCODE, ' + ; 'COUNTRY CUSTOMERS.COUNTRY, ' + ; 'PHONE CUSTOMERS.PHONE, ' + ; 'FAX CUSTOMERS.FAX' if.cursorfill(.t.) browse else aerror(laerrors) messagebox(laerrors[2]) endif.cursorfill(.t.) endwith The XML template this code references, CustomersByID.XML, looks like the following: <root xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <sql:header> <sql:param name="customerid"> </sql:param> </sql:header> <sql:query client-side-xml="0"> SELECT * FROM Customers WHERE CustomerID FOR XML AUTO </sql:query> </root> Place this file in a virtual directory for the Northwind database (see the appendix for details on configuring IIS to work with SQL Server). Here s the code for GetNWXML. It uses an MSXML2.XMLHTTP object to access a SQL Server 2000 XML template on a Web server and returns the results. The name of the template (and optionally any query parameters) is passed as a parameter to this code. lparameters tcurl local loxml as MSXML2.XMLHTTP loxml = createobject('msxml2.xmlhttp') loxml.open('post', ' + tcurl,.f.) loxml.setrequestheader('content-type', 'text/xml') loxml.send() return loxml.responsetext SendNWXML looks similar, except it expects to be passed a diffgram, loads the diffgram into an MSXML2.DOMDocument object, and passes that object to the Web Server, which will in turn pass it via SQLXML to SQL Server 2000 for processing. lparameters tcdiffgram local lodom as MSXML2.DOMDocument, ; loxml as MSXML2.XMLHTTP lodom = createobject('msxml2.domdocument') lodom.async =.F. lodom.loadxml(tcdiffgram) loxml = createobject('msxml2.xmlhttp') loxml.open('post', '

37 loxml.setrequestheader('content-type', 'text/xml') loxml.send(lodom) To see how this works, run XMLExample.prg. You should see a single record (the ALFKI customer) in a browse window. Change the value in some field, then close the window and run the PRG again. You should see that your change was written to the backend. CursorAdapter and DataEnvironment Subclasses As is normally the case in VFP, I ve created subclasses of CursorAdapter and DataEnvironment that I ll use rather than the base classes. SFCursorAdapter SFCursorAdapter (in SFDataClasses.vcx) is a subclass of CursorAdapter that has some additional functionality added: It can automatically handle parameterized queries; you can define the parameter values as static (a constant value) or dynamic (an expression, such as =Thisform.txtName.Value, that s evaluated when the cursor is opened or refreshed). It can automatically create indexes on the cursor after it s opened. It does some special things for ADO, such as setting DataSource to an ADO RecordSet, setting the ActiveConnection property of the RecordSet to an ADO Connection object, and creating and passing an ADO Command object to CursorFill when a parameterized query is used. It provides some simple error handling (the cerrormessage property is filled with the error message). It has Update and Release methods that are missing in CursorAdapter. Let s take a look at this class. The Init method creates two collections (using the new Collection base class, which maintains collections of things), one for parameters that may be needed for the SelectCmd property, and one for tags that should be created automatically after the cursor is opened. It also sets MULTILOCKS on, since that s required for CursorAdapter cursors. with This * Create parameters and tags collections..oparameters = createobject('collection').otags = createobject('collection') * Ensure MULTILOCKS is set on.

38 set multilocks on endwith The AddParameter method adds a parameter to the parameters collection. Pass this method the name of the parameter (this should match the name as it appears in the SelectCmd property) and optionally the value of the parameter (if you don t pass it now, you can set it later by using the GetParameter method). This code shows a couple of new features in VFP 8: the new Empty base class, which has no PEMs, making it ideal for lightweight objects, and the ADDPROPERTY() function, which acts like the AddProperty method for those objects that don t have that method. lparameters tcname, ; tuvalue local loparameter loparameter = createobject('empty') addproperty(loparameter, 'Name', tcname) addproperty(loparameter, 'Value', tuvalue) This.oParameters.Add(loParameter, tcname) Use the GetParameter method to return a specific parameter object; this is normally used when you want to set the value to use for the parameter. lparameters tcname local loparameter loparameter = This.oParameters.Item(tcName) return loparameter The SetConnection method is used to set the DataSource property to the desired connection. If DataSourceType is ODBC, pass the connection handle. If it s ADO, DataSource needs to be an ADO RecordSet with its ActiveConnection property set to an open ADO Connection object, so pass the Connection object and SetConnection will create the RecordSet and set its ActiveConnection to the passed object. lparameters tuconnection with This do case case.datasourcetype = 'ODBC'.DataSource = tuconnection case.datasourcetype = 'ADO'.DataSource = createobject('adodb.recordset').datasource.activeconnection = tuconnection endcase endwith To create the cursor, call the GetData method rather than CursorFill, since it handles parameters and errors automatically. Pass.T. to GetData if you want the cursor created but not filled with data. The first thing this method does is create privately-scoped variables with the same names and values as the parameters defined in the parameters collection (the GetParameterValue method called from here returns either the Value of the parameter object or the evaluation of the Value if it starts with an = ). Next, if we re using ADO and there are any parameters, the code creates an ADO Command object and sets its ActiveConnection to the Connection object, then passes the Command object to the CursorFill method; the CursorAdapter requires this for

39 parameterized ADO queries. If we re not using ADO or we don t have any parameters, the code simply calls CursorFill to fill the cursor. Note that.t. is passed to CursorFill, telling it to use CursorSchema, if CursorSchema is filled in (this is the behavior I wish the base class had). If the cursor was created, the code calls the CreateTags method to create the desired indexes for the cursor; if not, it calls the HandleError method to handle any errors that occurred. lparameters tlnodata local loparameter, ; lcname, ; luvalue, ; lluseschema, ; locommand, ; llreturn with This * If we're supposed to fill the cursor (as opposed to creating an empty one), * create variables to hold any parameters. We have to do it here rather than * in a method since we want them to be scoped as private. if not tlnodata for each loparameter in.oparameters lcname = loparameter.name luvalue =.GetParameterValue(loParameter) store luvalue to (lcname) next loparameter endif not tlnodata * If we're using ADO and there are any parameters, we need a Command object * to handle this. lluseschema = not empty(.cursorschema) if '?' $.SelectCmd and (.DataSourceType = 'ADO' or (.UseDEDataSource and ;.Parent.DataSourceType = 'ADO')) locommand = createobject('adodb.command') locommand.activeconnection = iif(.usededatasource, ;.Parent.DataSource.ActiveConnection,.DataSource.ActiveConnection) llreturn =.CursorFill(llUseSchema, tlnodata,.noptions, locommand) else * Try to fill the cursor. llreturn =.CursorFill(llUseSchema, tlnodata,.noptions) endif '?' $.SelectCmd... * If we created the cursor, create any tags defined for it. If not, handle * the error. if llreturn.createtags() else.handleerror() endif llreturn endwith return llreturn

40 The Requery method is similar to GetData, except that it refreshes the data in the cursor. Call this method rather than CursorRefresh because, as with GetData, it handles parameters and errors. local loparameter, ; lcname, ; luvalue, ; llreturn with This * Create variables to hold any parameters. We have to do it here rather than * in a method since we want them to be scoped as private. for each loparameter in.oparameters lcname = loparameter.name luvalue =.GetParameterValue(loParameter) store luvalue to (lcname) next loparameter * Refresh the cursor; handle any error if it failed. llreturn =.CursorRefresh() if not llreturn.handleerror() endif not llreturn endwith return llreturn The Update method is simple: it simply calls TABLEUPDATE() to attempt to update the original data source and calls HandleError if it failed. local llreturn llreturn = tableupdate(1,.f., This.Alias) if not llreturn This.HandleError() endif not llreturn return llreturn There are a few methods we won t look at here; feel free to examine them yourself. AddTag adds information about an index you want created after the cursor is created to the tags collection, while CreateTags, which is called from GetData, uses the information in that collection in INDEX ON statements. HandleError uses AERROR() to determine what went wrong and puts the second element of the error array into the cerrormessage property. Let s look at a couple of examples of using this class. The first one (taken from TestCursorAdapter.prg) gets all records from the Customers table in the Northwind database. This code isn t all that different from what you d use for a base class CursorAdapter (you d have to pass.f. for the first parameter to CursorFill since the CursorSchema isn t filled in). locursor = newobject('sfcursoradapter', 'SFDataClasses') with locursor * Connect to the SQL Server Northwind database and get customers records.

41 .DataSourceType = 'ODBC'.DataSource = sqlstringconnect('driver=sql Server;server=(local);' + ; 'database=northwind;uid=sa;pwd=;trusted_connection=no').alias = 'Customers'.SelectCmd = 'select * from customers' if.getdata() browse else messagebox('could not get the data. The error message was:' + ; chr(13) + chr(13) +.cerrormessage) endif.getdata() endwith The next example (also taken from TestCursorAdapter.prg) uses the ODBC version of the SFConnectionMgr, which we looked at in the Data Strategies in VFP: Introduction document, to manage the connection. It also uses a parameterized statement for SelectCmd, shows how the AddParameter method allows you to handle parameters, and demonstrates how you can automatically create tags for the cursor using the AddTag method. loconnmgr = newobject('sfconnectionmgrodbc', 'SFRemote') with loconnmgr.cdriver = 'SQL Server'.cServer = '(local)'.cdatabase = 'Northwind'.cUserName = 'sa'.cpassword = '' endwith if loconnmgr.connect() locursor = newobject('sfcursoradapter', 'SFDataClasses') with locursor.datasourcetype = 'ODBC'.SetConnection(loConnMgr.GetConnection()).Alias = 'Customers'.SelectCmd = 'select * from customers where country =?pcountry'.addparameter('pcountry', 'Brazil').AddTag('CustomerID', 'CustomerID').AddTag('Company', 'upper(companyname)').addtag('contact', 'upper(contactname)') if.getdata() messagebox('brazilian customers in CustomerID order') set order to CustomerID go top browse messagebox('brazilian customers in Contact order') set order to Contact go top browse messagebox('canadian customers') loparameter =.GetParameter('pcountry') loparameter.value = 'Canada'.Requery() browse else messagebox('could not get the data. The error message was:' + ;

42 chr(13) + chr(13) +.cerrormessage) endif.getdata() endwith else messagebox(loconnmgr.cerrormessage) endif loconnmgr.connect() SFDataEnvironment SFDataEnvironment (also in SFDataClasses.vcx) is much simpler than SFCursorAdapter, but has some useful functionality added: The GetData method calls the GetData method of all SFCursorAdapter members so you don t have to call them individually. Similarly, the Requery and Update methods call the Requery and Update methods of every SFCursorAdapter member. Like SFCursorAdapter, the SetConnection method sets DataSource to an ADO RecordSet and sets the ActiveConnection property of the RecordSet to an ADO Connection object. However, it also calls the SetConnection method of any SFCursorAdapter member that has UseDEDataSource set to.f. It provides some simple error handling (a cerrormessage property is filled with the error message). It has a Release method. GetData is very simple: it just calls the GetData method of any member object that has that method. lparameters tlnodata local locursor, ; llreturn for each locursor in This.Objects if pemstatus(locursor, 'GetData', 5) llreturn = locursor.getdata(tlnodata) if not llreturn This.cErrorMessage = locursor.cerrormessage exit endif not llreturn endif pemstatus(locursor, 'GetData', 5) next locursor return llreturn SetConnection is a little more complicated: it calls the SetConnection method of any member object that has that method and that has UseDEDataSource set to.f., then uses code similar to that in SFCursorAdapter to set its own DataSource if any of the CursorAdapters have UseDEDataSource set to.t.

43 lparameters tuconnection local llsetours, ; locursor, ; llreturn with This * Call the SetConnection method of any CursorAdapter that isn't using our * DataSource. llsetours =.F. for each locursor in.objects do case case upper(locursor.baseclass) <> 'CURSORADAPTER' case locursor.usededatasource llsetours =.T. case pemstatus(locursor, 'SetConnection', 5) locursor.setconnection(tuconnection) endcase next locursor * If we found any CursorAdapters that are using our DataSource, we'll need to * set our own. if llsetours do case case.datasourcetype = 'ODBC'.DataSource = tuconnection case.datasourcetype = 'ADO'.DataSource = createobject('adodb.recordset').datasource.activeconnection = tuconnection endcase endif llsetours endwith Requery and Update are almost identical to GetData, so we won t bother looking at them. TestDE.prg shows the use of SFDataEnvironment as a container for a couple of SFCursorAdapter classes. Since this example uses ADO, each SFCursorAdapter needs its own DataSource, so UseDEDataSource is set to.f. Notice that a single call to the DataEnvironment SetConnection method takes care of setting the DataSource property for each CursorAdapter. loconnmgr = newobject('sfconnectionmgrado', 'SFRemote') with loconnmgr.cdriver = 'SQLOLEDB.1'.cServer = '(local)'.cdatabase = 'Northwind'.cUserName = 'sa'.cpassword = '' endwith if loconnmgr.connect() lode = newobject('sfdataenvironment', 'SFDataClasses') with lode.newobject('customerscursor', 'SFCursorAdapter', 'SFDataClasses') with.customerscursor.alias = 'Customers'

44 .SelectCmd = 'select * from customers'.datasourcetype = 'ADO' endwith.newobject('orderscursor', 'SFCursorAdapter', 'SFDataClasses') with.orderscursor.alias = 'Orders'.SelectCmd = 'select * from orders'.datasourcetype = 'ADO' endwith.setconnection(loconnmgr.getconnection()) if.getdata() select Customers browse nowait select Orders browse else messagebox('could not get the data. The error message was:' + ; chr(13) + chr(13) +.cerrormessage) endif.getdata() endwith else messagebox(loconnmgr.cerrormessage) endif loconnmgr.connect() Reusable Data Classes Now that we have CursorAdapter and DataEnvironment subclasses to work with, let s talk about reusable data classes. One thing VFP developers have asked Microsoft to add to VFP for a long time is reusable data environments. For example, you may have a form and a report that have exactly the same data setup, but you have to manually fill in the DataEnvironment for each one because DataEnvironments aren t reusable. Some developers (and almost all frameworks vendors) made it easier to create reusable DataEnvironments by creating DataEnvironments in code (they couldn t be subclassed visually) and using a loader object on the form to instantiate the DataEnvironment subclass. However, this was kind of a kludge and didn t help with reports. Now, in VFP 8, we have the ability to create both reusable data classes, which can provide cursors from any data source to anything that needs them, and reusable DataEnvironments, which can host the data classes. As of this writing, you can t use CursorAdapter or DataEnvironment subclasses in a report, but you can programmatically add CursorAdapter subclasses (such as in the Init method of the DataEnvironment) to take advantage of reusability there. Let s create data classes for the Northwind Customers and Orders tables. First, create a subclass of SFCursorAdapter called CustomersCursor and set the properties as shown below. Property Value

45 Alias CursorSchema KeyFieldList SelectCmd Tables UpdatableFieldList UpdateNameList Customers CUSTOMERID C(5), COMPANYNAME C(40), CONTACTNAME C(30), CONTACTTITLE C(30), ADDRESS C(60), CITY C(15), REGION C(15), POSTALCODE C(10), COUNTRY C(15), PHONE C(24), FAX C(24) CUSTOMERID select * from customers CUSTOMERS CUSTOMERID, COMPANYNAME, CONTACTNAME, CONTACTTITLE, ADDRESS, CITY, REGION, POSTALCODE, COUNTRY, PHONE, FAX CUSTOMERID CUSTOMERS.CUSTOMERID, COMPANYNAME CUSTOMERS.COMPANYNAME, CONTACTNAME CUSTOMERS.CONTACTNAME, CONTACTTITLE CUSTOMERS.CONTACTTITLE, ADDRESS CUSTOMERS.ADDRESS, CITY CUSTOMERS.CITY, REGION CUSTOMERS.REGION, POSTALCODE CUSTOMERS.POSTALCODE, COUNTRY CUSTOMERS.COUNTRY, PHONE CUSTOMERS.PHONE, FAX, CUSTOMERS.FAX Note: you can use the CursorAdapter Builder to do most of the work, especially setting the CursorSchema and update properties. The trick is to turn on the use connection settings in builder only option, fill in the connection information so you have a live connection, then fill in the SelectCmd and use the builder to build the rest of the properties for you. Now, anytime you need records from the Northwind Customers table, you simply use the CustomersCursor class. Of course, we haven t defined any connection information, but that s actually a good thing, since this class shouldn t have to worry about things like how to get the data (ODBC, ADO, or XML) or even what database engine to use (there are Northwind databases for SQL Server, Access, and, new in version 8, VFP).

46 However, notice that this cursor deals with all records in the Customers table. Sometimes, you only want a specific customer. So, let s create a subclass of CustomersCursor called CustomerByIDCursor. Change SelectCmd to select * from customers where customerid =?pcustomerid and put the following code into Init: lparameters tccustomerid dodefault() This.AddParameter('pCustomerID', tccustomerid) This creates a parameter called pcustomerid (which is the same name as the one specified in SelectCmd) and sets it to any value passed. If no value is passed, use GetParameter to return an object for this parameter and set its Value property before calling GetData. Create an OrdersCursor class similar to CustomersCursor except that it retrieves all records from the Orders table. Then create an OrdersForCustomerCursor subclass that retrieves only those orders for a specific customer. Set SelectCmd to select * from orders where customerid =?pcustomerid and put the same code into Init as CustomerByIDCursor has (since it s the same parameter). To test how this works, run TestCustomersCursor.prg. Example: Form Now that we have some reusable data classes, let s put them to use. First, let s create a subclass of SFDataEnvironment called CustomersAndOrdersDataEnvironment that contains CustomerByIDCursor and OrdersForCustomerCursor classes. Set AutoOpenTables to.f. (because we need to set connection information before the tables can be opened) and UseDEDataSource for both CursorAdapters to.t. This DataEnvironment can now be used in a form to show information about a specific customer, including its orders. Let s create such a form. Create a form called CustomerOrders.scx (it s included with the sample files accompanying this document), set DEClass and DEClassLibrary to CustomersAndOrdersDataEnvironment so we use our reusable DataEnvironment. Put the following code into the Load method: #define ccdatasourcetype 'ADO' with This.CustomersAndOrdersDataEnvironment * Set the DataEnvironment data source..datasourcetype = ccdatasourcetype * If we're using ODBC or ADO, create a connection manager and open a * connection to the Northwind database. if.datasourcetype $ 'ADO,ODBC' This.AddProperty('oConnMgr') This.oConnMgr = newobject('sfconnectionmgr' + ccdatasourcetype, ; 'SFRemote') with This.oConnMgr.cDriver = iif(ccdatasourcetype = 'ADO', 'SQLOLEDB.1', ;

47 'SQL Server').cServer = '(local)'.cdatabase = 'Northwind'.cUserName = 'sa'.cpassword = '' endwith if not This.oConnMgr.Connect() messagebox(oconnmgr.cerrormessage) return.f. endif not This.oConnMgr.Connect() * If we're using ADO, each cursor must have its own datasource. if.datasourcetype = 'ADO'.CustomerByIDCursor.UseDEDataSource =.F..CustomerByIDCursor.DataSourceType = 'ADO'.OrdersForCustomerCursor.UseDEDataSource =.F..OrdersForCustomerCursor.DataSourceType = 'ADO' endif.datasourcetype = 'ADO' * Set the DataSource to the connection..setconnection(this.oconnmgr.getconnection()) * If we're using XML, change the SelectCmd to call the GetNWXML function * instead. else.customerbyidcursor.selectcmd = 'GetNWXML([customersbyid.xml?' + ; 'customerid=] + pcustomerid)'.customerbyidcursor.updatecmddatasourcetype = 'XML'.CustomerByIDCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)'.OrdersForCustomerCursor.SelectCmd = 'GetNWXML([ordersforcustomer.' + ; 'xml?customerid=] + pcustomerid)'.ordersforcustomercursor.updatecmddatasourcetype = 'XML'.OrdersForCustomerCursor.UpdateCmd = 'SendNWXML(This.UpdateGram)' endif.datasourcetype $ 'ADO,ODBC' * Specify that the value for the cursor parameters will be filled from the * CustomerID textbox. loparameter =.CustomerByIDCursor.GetParameter('pCustomerID') loparameter.value = '=Thisform.txtCustomerID.Value' loparameter =.OrdersForCustomerCursor.GetParameter('pCustomerID') loparameter.value = '=Thisform.txtCustomerID.Value' * Create empty cursors and display an error message if we failed. if not.getdata(.t.) messagebox(.cerrormessage) return.f. endif not.getdata(.t.) endwith This looks like a lot of code, but most of it is there for demo purposes to allow switching to different data access mechanisms.

48 This code creates a connection manager to handle the connection, either ADO, ODBC, or XML, depending on the ccdatasourcetype constant, which you can change to try each of the mechanisms. In the case of ADO, since each CursorAdapter must have its own DataSource, the UseDEDataSource and DataSourceType properties are set for each one. The code then calls the SetConnection method to set up the connection information. In the case of XML, the SelectCmd, UpdateCmdDataSourceType, and UpdateCmd properties must be changed as discussed earlier. Next, the code uses the GetParameter method of both CursorAdapter objects to set the Value of the pcustomerid parameter to the contents of a textbox in the form. Note the use of the = in the value; this means the Value property will be evaluated every time it s needed, so we essentially have a dynamic parameter (saving the need to constantly change the parameter to the current value as the user types in the textbox). Finally, the GetData method is called to create empty cursors so the data binding of the controls will work. Drop a textbox on the form and name it txtcustomerid. Put the following code into its Valid method: with Thisform.CustomersAndOrdersDataEnvironment.Requery().Refresh() endwith This causes the cursors to be requeried and the controls to be refreshed when a customer ID is entered. Drop a label on the form, put it beside the textbox, and set its Caption to Customer ID. Drag the CompanyName, ContactName, Address, City, Region, PostalCode, and Country fields from the Customers cursor in the DataEnvironment to the form to create controls for these fields. Then select the OrderID, EmployeeID, OrderDate, RequiredDate, ShippedDate, ShipVia, and Freight fields in the Orders cursor and drag them to the form to create a grid. That s it. Run the form and enter ALFKI for the customer ID. When you tab out of the textbox, you should see the customer address information and orders appear. Try changing something about the customer or order, then closing the form, running it again, and entering ALFKI again. You should see that the changes you made were written to the backend database without any effort on your part. Cool, huh? That wasn t a lot more work than creating a form based on local tables or views. Even better, try changing the ccdatasourcetype constant to ADO or XML and notice that the form looks and works exactly the same. That s the whole point of CursorAdapters! Example: Report Let s try a report. The example discussed here was taken from CustomerOrders.frx that accompanies this document. The biggest issue here is that, unlike a form, we can t tell a report to use a DataEnvironment subclass, nor can we drop CursorAdapter subclasses in the DataEnvironment. So, we ll have to put some code into the report to add CursorAdapter subclasses to the DataEnvironment. Although it might seem logical to put this code into the

49 BeforeOpenTables event of the report s DataEnvironment, that won t actually work because for reasons I don t understand, BeforeOpenTables fires on every page when you preview the report. So, we ll put the code into the Init method. #define ccdatasourcetype 'ODBC' with This set safety off * Set the DataEnvironment data source..datasourcetype = ccdatasourcetype * Create CursorAdapter objects for Customers and Orders..NewObject('CustomersCursor', 'CustomersCursor', 'NorthwindDataClasses').CustomersCursor.AddTag('CustomerID', 'CustomerID').NewObject('OrdersCursor', 'OrdersCursor', 'NorthwindDataClasses').OrdersCursor.AddTag('CustomerID', 'CustomerID') * If we're using ODBC or ADO, create a connection manager and open a * connection to the Northwind database. if.datasourcetype $ 'ADO,ODBC'.AddProperty('oConnMgr').oConnMgr = newobject('sfconnectionmgr' + ccdatasourcetype, ; 'SFRemote') with.oconnmgr.cdriver = iif(ccdatasourcetype = 'ADO', 'SQLOLEDB.1', ; 'SQL Server').cServer = '(local)'.cdatabase = 'Northwind'.cUserName = 'sa'.cpassword = '' endwith if not.oconnmgr.connect() messagebox(.oconnmgr.cerrormessage) return.f. endif not.oconnmgr.connect() * If we're using ADO, each cursor must have its own datasource. if.datasourcetype = 'ADO'.CustomersCursor.UseDEDataSource =.F..CustomersCursor.DataSourceType = 'ADO'.CustomersCursor.SetConnection(.oConnMgr.GetConnection()).OrdersCursor.UseDEDataSource =.F..OrdersCursor.DataSourceType = 'ADO'.OrdersCursor.SetConnection(.oConnMgr.GetConnection()) else.customerscursor.usededatasource =.T..OrdersCursor.UseDEDataSource =.T..DataSource =.oconnmgr.getconnection() endif.datasourcetype = 'ADO'.CustomersCursor.SetConnection(.oConnMgr.GetConnection()).OrdersCursor.SetConnection(.oConnMgr.GetConnection())

50 * If we're using XML, change the SelectCmd to call the GetNWXML function * instead. else.customerscursor.selectcmd = 'GetNWXML([getallcustomers.xml])'.CustomersCursor.DataSourceType = 'XML'.OrdersCursor.SelectCmd = 'GetNWXML([getallorders.xml])'.OrdersCursor.DataSourceType = 'XML' endif.datasourcetype $ 'ADO,ODBC' * Get the data and display an error message if we failed. if not.customerscursor.getdata() messagebox(.customerscursor.cerrormessage) return.f. endif not.customerscursor.getdata() if not.orderscursor.getdata() messagebox(.orderscursor.cerrormessage) return.f. endif not.orderscursor.getdata() * Set a relation from Customers to Orders. set relation to CustomerID into Customers endwith This code looks similar to that of the form. Again, the majority of the code is to handle different data access mechanisms. However, there is some additional code because we can t use a DataEnvironment subclass and have to code the behavior ourselves. Now, how do we conveniently put the fields on the report? Since the CursorAdapter don t exist in the DataEnvironment at design time, we can t just drag fields from them to the report. Here s a tip: create a PRG that creates the cursors and leaves them in scope (either by suspending or making the CursorAdapter objects public), then use the Quick Report function to put fields with the proper sizes on the report. Create a group on CUSTOMERS.CUSTOMERID and check Start each group on a new page. Then lay out the report to look similar to the following:

51 XMLAdapter In addition to CursorAdapter, VFP 8 has three new base classes to improve VFP s support for XML: XMLAdapter, XMLTable, and XMLField. XMLAdapter provides a means of converting data between XML and VFP cursors. It has a lot more capabilities than the CURSORTOXML() and XMLTOCURSOR() functions, including support for hierarchical XML and the ability to consume types of XML those function don t support such as ADO.NET DataSets. XMLTable and XMLField are child objects that provide the ability to fine-tune the schema for the XML data. In addition, XMLTable has an ApplyDiffgram method intended to allow VFP to consume updategrams and diffgrams, something that was missing from VFP 7. To give you an idea of its capabilities, I created an ASP.NET Web Service that returns an ADO.NET DataSet, and then used an XMLAdapter object in VFP to consume that DataSet. Here s now I did this. First, in Visual Studio.NET, I dragged the Northwind Customers table from the Server Explorer to a new ASP.NET Web Service project I called NWWebService. This automatically created two objects, SQLConnection1 and SQLDataAdapter1. I then added the following code to the existing generated code: <WebMethod()> Public Function GetAllCustomers() As DataSet Dim lodataset As New DataSet() Me.SqlConnection1.Open() Me.SqlDataAdapter1.Fill(loDataSet) Return lodataset End Function

52 I built the project to generate the appropriate Web Service files in the NWWebService virtual directory (which VS.NET automatically created for me). To consume this Web Service in VFP, I used the IntelliSense Manager to register a Web Service called Northwind.NET, pointing it at WSDL as the location of the WSDL file. I then created the following code (in XMLAdapterWebService.prg) to call the Web Service and convert the ADO.NET DataSet into a VFP cursor. local lows as Northwind.NET, ; loxmladapter as XMLAdapter, ; lotable as XMLTable * Get the.net DataSet from a.net Web Service. lows = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx") lows.cwsname = "Northwind.NET" lows = lows.setupclient(" + ; "?WSDL", "NWWebService", "NWWebServiceSoap") loxml = lows.getallcustomers() * Create an XMLAdapter and load the data. loxmladapter = createobject('xmladapter') loxmladapter.xmlschemalocation = '1' loxmladapter.loadxml(loxml.item(0).parentnode.xml) * If we succeeded in loading the XML, create and browse a cursor from each * table object. if loxmladapter.isloaded for each lotable in loxmladapter.tables lotable.tocursor() browse use next lotable endif loxmladapter.isloaded Note that in order to use XMLAdapter, you need MSXML 4.0 Service Pack 1 or later installed on your system. You can download this from the MSDN Web site (go to and search for MSXML). Summary I think CursorAdapter is one of the biggest and most exciting enhancements in VFP 8 because it provides a consistent and easy-to-use interface to remote data, plus it allows us to create reusable data classes. I m sure once you work with them, you ll find them as exciting as I do.

53 Biography Doug Hennig is a partner with Stonefield Systems Group Inc. He is the author of the awardwinning Stonefield Database Toolkit (SDT) and co-author of the award-winning Stonefield Query. He is co-author (along with Tamar Granor, Ted Roche, and Della Martin) of "The Hacker's Guide to Visual FoxPro 7.0" and co-author (along with Tamar Granor and Kevin McNeish) of "What's New in Visual FoxPro 7.0", both from Hentzenwerke Publishing, and author of "The Visual FoxPro Data Dictionary" in Pinnacle Publishing's Pros Talk Visual FoxPro series. He writes the monthly "Reusable Tools" column in FoxTalk. He was the technical editor of "The Hacker's Guide to Visual FoxPro 6.0" and "The Fundamentals", both from Hentzenwerke Publishing. Doug has spoken at every Microsoft FoxPro Developers Conference (DevCon) since 1997 and at user groups and developer conferences all over North America. He is a Microsoft Most Valuable Professional (MVP) and Certified Professional (MCP). Copyright 2002 Doug Hennig. All Rights Reserved Doug Hennig Partner Stonefield Systems Group Inc Winnipeg Street, Suite 200 Regina, SK Canada S4R 1J6 Phone: (306) Fax: (306) dhennig@stonefield.com Web:

54 Appendix: Setting Up SQL Server 2000 XML Access In order to access SQL Server 2000 using a URL in a browser or other HTTP client, you have to do a few things. First, you need to download and install SQLXML 3.0 from the MSDN Web site ( do a search for SQLXML and then choose the download link). Next, you have to set up an IIS virtual directory. To do this, choose the Configure IIS Support shortcut in the SQLXML 3.0 folder under Start, Programs in your Windows Taskbar. Expand the node for your server, choose the Web site to work with, and then right-click and choose New, Virtual Directory. In the General tab of the dialog that appears, enter the name of the virtual directory and its physical path. For the samples in this document, use Northwind as the virtual directory name and NorthwindTemplates for the physical directory. Using Windows Explorer, create the physical directory somewhere on your system, and create a subdirectory of it called Template (we ll use that subdirectory in a moment). Copy the templates files included with this article to the Template subdirectory.

55 In the Security tab, enter the appropriate information to access SQL Server, such as the user name and password or the specific authentication mechanism you want to use.

56 In the Data Source tab, choose the server and, if desired, the database to use. For the examples for this document, choose the Northwind database.

57 In the Settings tab, choose the desired settings, but at a minimum, turn on Allow Template Queries and Allow POST.

58 In the Virtual Names tab, choose template from the Type combobox and enter a virtual name (for this document, use template ) and physical path (which should be a subdirectory of the virtual directory; for this document, use Template ) to use for templates. Click on OK

59 To test this, bring up your browser and type the following URL: You should see something like the following:

Much ADO About Something Doug Hennig

Much ADO About Something Doug Hennig Much ADO About Something Doug Hennig The release of the OLE DB provider for VFP means better performance and scalability for applications that need to access VFP data via ADO. However, there are some interesting

More information

The Mother of All TreeViews, Part 2 Doug Hennig

The Mother of All TreeViews, Part 2 Doug Hennig The Mother of All TreeViews, Part 2 Doug Hennig Last month, Doug presented a reusable class that encapsulates most of the desired behavior for a TreeView control. He discussed controlling the appearance

More information

CATCH Me if You Can Doug Hennig

CATCH Me if You Can Doug Hennig CATCH Me if You Can Doug Hennig VFP 8 has structured error handling, featuring the new TRY... CATCH... FINALLY... ENDTRY structure. This powerful new feature provides a third layer of error handling and

More information

Taking Control Doug Hennig

Taking Control Doug Hennig Taking Control Doug Hennig This month, Doug Hennig discusses a simple way to make anchoring work the way you expect it to and how to control the appearance and behavior of a report preview window. There

More information

Your Free Issue! April 2008 Number 2 2 ADS Special Issue Advantage Database Server for Visual FoxPro Developers

Your Free Issue! April 2008 Number 2 2 ADS Special Issue Advantage Database Server for Visual FoxPro Developers Your Free Issue! April 2008 Number 2 2 ADS Special Issue Advantage Database Server for Visual FoxPro Developers Dear FoxPro Developer, Here is your free copy of the new magazine dedicated to FoxPro, FoxRockX!

More information

Splitting Up is Hard to Do Doug Hennig

Splitting Up is Hard to Do Doug Hennig Splitting Up is Hard to Do Doug Hennig While they aren t used everywhere, splitter controls can add a professional look to your applications when you have left/right or top/bottom panes in a window. This

More information

Role-Based Security, Part III Doug Hennig

Role-Based Security, Part III Doug Hennig Role-Based Security, Part III Doug Hennig This month, Doug Hennig winds up his series on role-based security by examining a form class responsible for maintaining users and roles. In my previous two articles,

More information

May I See Your License? Doug Hennig

May I See Your License? Doug Hennig May I See Your License? Doug Hennig If you develop commercial applications, you may want to provide multiple levels of your application depending on what your users paid for, and prevent unauthorized users

More information

Extending the VFP 9 IDE Doug Hennig

Extending the VFP 9 IDE Doug Hennig Extending the VFP 9 IDE Doug Hennig One of the key themes in VFP 9 is extensibility. You can extend the VFP 9 Report Designer through report events and the reporting engine through the new ReportListener

More information

Session V-STON Stonefield Query: The Next Generation of Reporting

Session V-STON Stonefield Query: The Next Generation of Reporting Session V-STON Stonefield Query: The Next Generation of Reporting Doug Hennig Overview Are you being inundated with requests from the users of your applications to create new reports or tweak existing

More information

A Generic Import Utility, Part 2

A Generic Import Utility, Part 2 A Generic Import Utility, Part 2 Doug Hennig Part 1 of this two-part series presented a set of classes making up a generic import utility you can add to your applications to provide import capabilities

More information

Base Classes Revisited Doug Hennig

Base Classes Revisited Doug Hennig Base Classes Revisited Doug Hennig Most VFP developers know you should never use the VFP base classes, but instead create your own set of base classes. It s time to blow the dust off the set of base classes

More information

Data Handling Issues, Part I Doug Hennig

Data Handling Issues, Part I Doug Hennig Data Handling Issues, Part I Doug Hennig The ability to handle multiple sets of data is a frequent requirement in business applications. So is the management of primary key values for tables. In this first

More information

Zip it, Zip it Good Doug Hennig

Zip it, Zip it Good Doug Hennig Zip it, Zip it Good Doug Hennig This month s article presents a class to zip and pack the files in a project, and a class to interface VFP with a zipping utility like WinZip. Combining these classes with

More information

IntelliSense at Runtime Doug Hennig

IntelliSense at Runtime Doug Hennig IntelliSense at Runtime Doug Hennig VFP 9 provides support for IntelliSense at runtime. This month, Doug Hennig examines why this is useful, discusses how to implement it, and extends Favorites for IntelliSense

More information

A New IDE Add-on: FoxTabs Doug Hennig

A New IDE Add-on: FoxTabs Doug Hennig A New IDE Add-on: FoxTabs Doug Hennig FoxTabs provides easy access to all open windows in your VFP IDE. However, not only is it a great tool, it uses some very cool techniques to do its magic, including

More information

Data-Drive Your Applications Doug Hennig

Data-Drive Your Applications Doug Hennig Data-Drive Your Applications Doug Hennig As VFP developers, we re used to storing application data in tables. However, another use for tables is to store information about application behavior. This month

More information

Manage Your Applications Doug Hennig

Manage Your Applications Doug Hennig Manage Your Applications Doug Hennig This month s article presents a simple yet useful tool, and discusses several reusable techniques used in this tool. If you re like me, your hard drive (or your server

More information

Christmas Stocking Stuffers Doug Hennig

Christmas Stocking Stuffers Doug Hennig Christmas Stocking Stuffers Doug Hennig Visual FoxPro has a lot more places to put code than FoxPro 2.x. This month s column examines the advantages and disadvantages of creating classes for library routines.

More information

uilding Your Own Builders with BuilderB Doug Hennig and Yuanitta Morhart

uilding Your Own Builders with BuilderB Doug Hennig and Yuanitta Morhart uilding Your Own Builders with BuilderB Doug Hennig and Yuanitta Morhart Builders make it easy to set properties for objects at design time, which is especially handy for containers which you normally

More information

More Flexible Reporting With XFRX Doug Hennig

More Flexible Reporting With XFRX Doug Hennig More Flexible Reporting With XFRX Doug Hennig XFRX can make your reporting solutions more flexible since it allows you to output FRX reports to PDF, Microsoft Word, Microsoft Excel, and HTML files. This

More information

Advanced Uses for Dynamic Form

Advanced Uses for Dynamic Form Advanced Uses for Dynamic Form Doug Hennig Dynamic Form is an under-used project in VFPX. Its ability to create forms quickly and dynamically isn t something every developer needs but if you need it, Dynamic

More information

Data Handling Issues, Part II Doug Hennig

Data Handling Issues, Part II Doug Hennig Data Handling Issues, Part II Doug Hennig While field and table validation rules protect your tables from invalid data, they also make data entry forms harder to use. In this second of a two-part article,

More information

Try Thor s Terrific Tools, Part 2

Try Thor s Terrific Tools, Part 2 Try Thor s Terrific Tools, Part 2 Thor offers lots of tools for working with classes and forms. Learning to use them can make you more productive. Tamar E. Granor, Ph.D. In my last article, I showed a

More information

Multi-User and Data Buffering Issues

Multi-User and Data Buffering Issues Multi-User and Data Buffering Issues Doug Hennig Partner Stonefield Systems Group Inc. 1112 Winnipeg Street, Suite 200 Regina, SK Canada S4R 1J6 Phone: (306) 586-3341 Fax: (306) 586-5080 Email: dhennig@stonefield.com

More information

A New Beginning Doug Hennig

A New Beginning Doug Hennig A New Beginning Doug Hennig The development world is moving to reusable components. As goes the world, so goes Doug s column. We ll start off the new Best Tools column with SFThermometer, a handy progress

More information

Custom UI Controls: Splitter

Custom UI Controls: Splitter Custom UI Controls: Splitter Doug Hennig Adding a splitter control to your forms gives them a more professional behavior and allows your users to decide the relative sizes of resizable controls. Over the

More information

I Got Rendered Where? Part II Doug Hennig

I Got Rendered Where? Part II Doug Hennig I Got Rendered Where? Part II Doug Hennig Thanks to the new ReportListener in VFP 9, we have a lot more control over where reports are rendered. Last month, Doug Hennig showed a listener that collaborates

More information

Cool Tools by Craig Boyd, Part II Doug Hennig

Cool Tools by Craig Boyd, Part II Doug Hennig Cool Tools by Craig Boyd, Part II Doug Hennig Doug Hennig continues his examination of cool tools provided to the VFP community by Craig Boyd. Last month, I discussed several tools generously provided

More information

Speed in Object Creation and. Destruction. March 2016 Number 49. Tamar E. Granor, Ph.D.

Speed in Object Creation and. Destruction. March 2016 Number 49. Tamar E. Granor, Ph.D. Speed in Object Creation and Destruction Does the approach you choose for creating and destroying objects have an impact on performance? Tamar E. Granor, Ph.D. March 2016 Number 49 1 Know How... Speed

More information

A File Open Dialog Doug Hennig

A File Open Dialog Doug Hennig A File Open Dialog Doug Hennig A File Open dialog is a better approach than having a long list of forms appear in the File menu. This article presents a reusable File Open dialog that supports grouping

More information

The Best of Both Worlds

The Best of Both Worlds The Best of Both Worlds Doug Hennig You don t have to sacrifice performance for ease of installing new versions of applications. Using STARTAPP, you can run applications from local drives for best performance,

More information

Windows Event Binding Made Easy Doug Hennig

Windows Event Binding Made Easy Doug Hennig Windows Event Binding Made Easy Doug Hennig A feature available in other development environments but missing in VFP is the ability to capture Windows events. VFP 9 extends the BINDEVENT() function to

More information

Active Server Pages Architecture

Active Server Pages Architecture Active Server Pages Architecture Li Yi South Bank University Contents 1. Introduction... 2 1.1 Host-based databases... 2 1.2 Client/server databases... 2 1.3 Web databases... 3 2. Active Server Pages...

More information

Web Page Components Doug Hennig

Web Page Components Doug Hennig Web Page Components Doug Hennig With its fast and powerful string functions, VFP is a great tool for generating HTML. This month, Doug Hennig shows how you can treat Web pages as a collection of reusable

More information

In this chapter, I m going to show you how to create a working

In this chapter, I m going to show you how to create a working Codeless Database Programming In this chapter, I m going to show you how to create a working Visual Basic database program without writing a single line of code. I ll use the ADO Data Control and some

More information

Extending the VFP 9 Reporting System, Part I: Design-Time

Extending the VFP 9 Reporting System, Part I: Design-Time Extending the VFP 9 Reporting System, Part I: Design-Time Doug Hennig Stonefield Software Inc. 1112 Winnipeg Street, Suite 200 Regina, SK Canada S4R 1J6 Voice: 306-586-3341 Fax: 306-586-5080 Email: dhennig@stonefield.com

More information

Instructor: Craig Duckett. Lecture 14: Tuesday, May 15 th, 2018 Stored Procedures (SQL Server) and MySQL

Instructor: Craig Duckett. Lecture 14: Tuesday, May 15 th, 2018 Stored Procedures (SQL Server) and MySQL Instructor: Craig Duckett Lecture 14: Tuesday, May 15 th, 2018 Stored Procedures (SQL Server) and MySQL 1 Assignment 3 is due LECTURE 20, Tuesday, June 5 th Database Presentation is due LECTURE 20, Tuesday,

More information

Windows Database Applications

Windows Database Applications 3-1 Windows Database Applications Chapter 3 In this chapter, you learn to access and display database data on a Windows form. You will follow good OOP principles and perform the database access in a datatier

More information

W h i t e P a p e r. Integration Overview Importing Data and Controlling BarTender from Within Other Programs

W h i t e P a p e r. Integration Overview Importing Data and Controlling BarTender from Within Other Programs W h i t e P a p e r Integration Overview Importing Data and Controlling BarTender from Within Other Programs Contents Contents...2 Introduction...3 Selecting the Desired Label Data...3 Why you Usually

More information

FoxTalk. GOING through the classes provided in the FoxPro. More Gems in the FFC. Doug Hennig 6.0

FoxTalk. GOING through the classes provided in the FoxPro. More Gems in the FFC. Doug Hennig 6.0 FoxTalk Solutions for Microsoft FoxPro and Visual FoxPro Developers This is an exclusive supplement for FoxTalk subscribers. For more information about FoxTalk, call us at 1-800-788-1900 or visit our Web

More information

XP: Backup Your Important Files for Safety

XP: Backup Your Important Files for Safety XP: Backup Your Important Files for Safety X 380 / 1 Protect Your Personal Files Against Accidental Loss with XP s Backup Wizard Your computer contains a great many important files, but when it comes to

More information

pdating an Application over the Internet, Part II Doug Hennig

pdating an Application over the Internet, Part II Doug Hennig pdating an Application over the Internet, Part II Doug Hennig This second of a two-part series looks at several strategies for automating the process of distributing application updates by downloading

More information

Making the Most of the Toolbox

Making the Most of the Toolbox Making the Most of the Toolbox Session 15 Tamar E. Granor, Ph.D. Tomorrow s Solutions, LLC 8201 Cedar Road Elkins Park, PA 19027 Phone: 215-635-1958 Email: tamar@tomorrowssolutionsllc.com Web: www.tomorrowssolutionsllc.com

More information

C# Programming: From Problem Analysis to Program Design 2nd Edition. David McDonald, Ph.D. Director of Emerging Technologies. Objectives (1 of 2)

C# Programming: From Problem Analysis to Program Design 2nd Edition. David McDonald, Ph.D. Director of Emerging Technologies. Objectives (1 of 2) 13 Database Using Access ADO.NET C# Programming: From Problem Analysis to Program Design 2nd Edition David McDonald, Ph.D. Director of Emerging Technologies Objectives (1 of 2) Retrieve and display data

More information

## Version: FoxPro 7.0 ## Figures: ## File for Subscriber Downloads: Publishing Your First Web Service Whil Hentzen

## Version: FoxPro 7.0 ## Figures: ## File for Subscriber Downloads: Publishing Your First Web Service Whil Hentzen ## Version: FoxPro 7.0 ## Figures: ## File for Subscriber Downloads: Publishing Your First Web Service Whil Hentzen Web Services The Buzzword of the 02s! It s nothing really new, however, any more than

More information

Handling crosstabs and other wide data in VFP reports

Handling crosstabs and other wide data in VFP reports Handling crosstabs and other wide data in VFP reports When the data you want to report on has many columns, you have a few options. One takes advantage of VFP s fl exibility. Tamar E. Granor, Ph.D. In

More information

Generating crosstabs in VFP

Generating crosstabs in VFP Generating crosstabs in VFP Several tools make it easy to change from normalized data to crosstabbed data with both rows and columns determined by the data in the set. Tamar E. Granor, Ph.D. One of the

More information

ADO.NET from 3,048 meters

ADO.NET from 3,048 meters C H A P T E R 2 ADO.NET from 3,048 meters 2.1 The goals of ADO.NET 12 2.2 Zooming in on ADO.NET 14 2.3 Summary 19 It is a rare opportunity to get to build something from scratch. When Microsoft chose the

More information

Splitting a Procedure File

Splitting a Procedure File Splitting a Procedure File It s easier to maintain separate program files rather than one monolithic procedure file. This utility makes it easy. Tamar E. Granor, Ph.D. Procedure files have been part of

More information

Part I: Programming Access Applications. Chapter 1: Overview of Programming for Access. Chapter 2: Extending Applications Using the Windows API

Part I: Programming Access Applications. Chapter 1: Overview of Programming for Access. Chapter 2: Extending Applications Using the Windows API 74029c01.qxd:WroxPro 9/27/07 1:43 PM Page 1 Part I: Programming Access Applications Chapter 1: Overview of Programming for Access Chapter 2: Extending Applications Using the Windows API Chapter 3: Programming

More information

Report Objects Doug Hennig

Report Objects Doug Hennig Report Objects Doug Hennig FoxPro developers have wanted an object-oriented report writer since VFP 3 was released. Although the classes presented in this article don t have a pretty visual designer tool,

More information

Consolidate data from a field into a list

Consolidate data from a field into a list Consolidate data from a field into a list This task is hard in VFP, but SQL Server provides two ways to do it. Tamar E. Granor, Ph.D. Some SQL commands were added to FoxPro 2.0 and I fell in love with

More information

VFP: Ideal for Tools, Part 3

VFP: Ideal for Tools, Part 3 VFP: Ideal for Tools, Part 3 The VFP language supports programmatic manipulation of programs and projects, providing more options for creating developer tools. Tamar E. Granor, Ph.D. The first two parts

More information

anguage Enhancements in VFP 7, Part I Doug Hennig

anguage Enhancements in VFP 7, Part I Doug Hennig anguage Enhancements in VFP 7, Part I Doug Hennig Want an early peek at what s new in VFP 7? This is the first article in a series discussing the language enhancements Microsoft is implementing and how

More information

Putting Parameters in Perspective Doug Hennig

Putting Parameters in Perspective Doug Hennig Putting Parameters in Perspective Doug Hennig Concentrating on communication between routines can reduce up to one-third of the errors in your applications. This month s column examines some Best Practices

More information

Give Thor Tools Options

Give Thor Tools Options Give Thor Tools Options The framework for specifying and using options for Thor Tools is elegant and easy to use. Tamar E. Granor, Ph.D. In my last article, I showed how to add your own tools to Thor.

More information

The West-Wind Web Connection wwbusiness Class

The West-Wind Web Connection wwbusiness Class The West-Wind Web Connection wwbusiness Class by Kevin J. Cully Cully Technologies, LLC Presented to the Atlanta FoxPro Users Group March 21 st, 2002 5090 Hampton s Club Dr. Alpharetta, GA 30004 770-781-8270

More information

LeakDAS Version 4 The Complete Guide

LeakDAS Version 4 The Complete Guide LeakDAS Version 4 The Complete Guide SECTION 4 LEAKDAS MOBILE Second Edition - 2014 Copyright InspectionLogic 2 Table of Contents CONNECTING LEAKDAS MOBILE TO AN ANALYZER VIA BLUETOOTH... 3 Bluetooth Devices...

More information

Introduction to MySQL. Database Systems

Introduction to MySQL. Database Systems Introduction to MySQL Database Systems 1 Agenda Bureaucracy Database architecture overview Buzzwords SSH Tunneling Intro to MySQL Comments on homework 2 Homework #1 Submission date is on the website..

More information

ONE of the challenges we all face when developing

ONE of the challenges we all face when developing Using SQL-DMO to Handle Security in ADPs Russell Sinclair 2000 2002 Smart Access MSDE is a powerful, free version of SQL Server that you can make available to your users. In this issue, Russell Sinclair

More information

Word: Print Address Labels Using Mail Merge

Word: Print Address Labels Using Mail Merge Word: Print Address Labels Using Mail Merge No Typing! The Quick and Easy Way to Print Sheets of Address Labels Here at PC Knowledge for Seniors we re often asked how to print sticky address labels in

More information

Migrating Your Visual FoxPro Application to a Client/Server Platform

Migrating Your Visual FoxPro Application to a Client/Server Platform White Paper Migrating Your Visual FoxPro Application to a Client/Server Platform By Daniel R. LeClair, Senior Developer Abstract This white paper details issues you may encounter in migrating your monolithic

More information

Access and Assign Methods in VFP

Access and Assign Methods in VFP Access and Assign Methods in VFP By Doug Hennig Introduction In my opinion, one of the most useful and powerful additions in Visual FoxPro 6 is access and assign methods. It may seem strange that something

More information

We aren t getting enough orders on our Web site, storms the CEO.

We aren t getting enough orders on our Web site, storms the CEO. In This Chapter Introducing how Ajax works Chapter 1 Ajax 101 Seeing Ajax at work in live searches, chat, shopping carts, and more We aren t getting enough orders on our Web site, storms the CEO. People

More information

Burning CDs in Windows XP

Burning CDs in Windows XP B 770 / 1 Make CD Burning a Breeze with Windows XP's Built-in Tools If your PC is equipped with a rewritable CD drive you ve almost certainly got some specialised software for copying files to CDs. If

More information

Excel Basics Rice Digital Media Commons Guide Written for Microsoft Excel 2010 Windows Edition by Eric Miller

Excel Basics Rice Digital Media Commons Guide Written for Microsoft Excel 2010 Windows Edition by Eric Miller Excel Basics Rice Digital Media Commons Guide Written for Microsoft Excel 2010 Windows Edition by Eric Miller Table of Contents Introduction!... 1 Part 1: Entering Data!... 2 1.a: Typing!... 2 1.b: Editing

More information

Excel Basics: Working with Spreadsheets

Excel Basics: Working with Spreadsheets Excel Basics: Working with Spreadsheets E 890 / 1 Unravel the Mysteries of Cells, Rows, Ranges, Formulas and More Spreadsheets are all about numbers: they help us keep track of figures and make calculations.

More information

Oracle SQL. murach s. and PL/SQL TRAINING & REFERENCE. (Chapter 2)

Oracle SQL. murach s. and PL/SQL TRAINING & REFERENCE. (Chapter 2) TRAINING & REFERENCE murach s Oracle SQL and PL/SQL (Chapter 2) works with all versions through 11g Thanks for reviewing this chapter from Murach s Oracle SQL and PL/SQL. To see the expanded table of contents

More information

Using Images in FF&EZ within a Citrix Environment

Using Images in FF&EZ within a Citrix Environment 1 Using Images in FF&EZ within a Citrix Environment This document explains how to add images to specifications, and covers the situation where the FF&E database is on a remote server instead of your local

More information

Welcome Back! Without further delay, let s get started! First Things First. If you haven t done it already, download Turbo Lister from ebay.

Welcome Back! Without further delay, let s get started! First Things First. If you haven t done it already, download Turbo Lister from ebay. Welcome Back! Now that we ve covered the basics on how to use templates and how to customise them, it s time to learn some more advanced techniques that will help you create outstanding ebay listings!

More information

An Incredibly Brief Introduction to Relational Databases: Appendix B - Learning Rails

An Incredibly Brief Introduction to Relational Databases: Appendix B - Learning Rails O'Reilly Published on O'Reilly (http://oreilly.com/) See this if you're having trouble printing code examples An Incredibly Brief Introduction to Relational Databases: Appendix B - Learning Rails by Edd

More information

anguage Enhancements in VFP 7, Part V Doug Hennig

anguage Enhancements in VFP 7, Part V Doug Hennig anguage Enhancements in VFP 7, Part V Doug Hennig This fifth in a series of articles on language enhancements in VFP 7 covers the remaining new general-purpose commands and functions. GETWORDCOUNT() and

More information

Sage Abra Alerts is designed and licensed exclusively for use with the Sage Abra HRMS database.

Sage Abra Alerts is designed and licensed exclusively for use with the Sage Abra HRMS database. Release Notes Product: Sage Abra Alerts Version: 5.1 Overview Upgrade Information Sage Abra Alerts is designed and licensed exclusively for use with the Sage Abra HRMS database. Sage requires that Sage

More information

Creating a new form with check boxes, drop-down list boxes, and text box fill-ins. Customizing each of the three form fields.

Creating a new form with check boxes, drop-down list boxes, and text box fill-ins. Customizing each of the three form fields. In This Chapter Creating a new form with check boxes, drop-down list boxes, and text box fill-ins. Customizing each of the three form fields. Adding help text to any field to assist users as they fill

More information

Linking Reports to your Database in Crystal Reports 2008

Linking Reports to your Database in Crystal Reports 2008 Linking Reports to your Database in Crystal Reports 2008 After downloading and saving a report on your PC, either (1) browse-to the report using Windows Explorer and double-click on the report file or

More information

» How do I Integrate Excel information and objects in Word documents? How Do I... Page 2 of 10 How do I Integrate Excel information and objects in Word documents? Date: July 16th, 2007 Blogger: Scott Lowe

More information

Chapter 18 Outputting Data

Chapter 18 Outputting Data Chapter 18: Outputting Data 231 Chapter 18 Outputting Data The main purpose of most business applications is to collect data and produce information. The most common way of returning the information is

More information

It was a dark and stormy night. Seriously. There was a rain storm in Wisconsin, and the line noise dialing into the Unix machines was bad enough to

It was a dark and stormy night. Seriously. There was a rain storm in Wisconsin, and the line noise dialing into the Unix machines was bad enough to 1 2 It was a dark and stormy night. Seriously. There was a rain storm in Wisconsin, and the line noise dialing into the Unix machines was bad enough to keep putting garbage characters into the command

More information

Dynamics ODBC REFERENCE Release 5.5a

Dynamics ODBC REFERENCE Release 5.5a Dynamics ODBC REFERENCE Release 5.5a Copyright Manual copyright 1999 Great Plains Software, Inc. All rights reserved. This document may not, in whole or in any part, be copied, photocopied, reproduced,

More information

Chapter01.fm Page 1 Monday, August 23, :52 PM. Part I of Change. The Mechanics. of Change

Chapter01.fm Page 1 Monday, August 23, :52 PM. Part I of Change. The Mechanics. of Change Chapter01.fm Page 1 Monday, August 23, 2004 1:52 PM Part I The Mechanics of Change The Mechanics of Change Chapter01.fm Page 2 Monday, August 23, 2004 1:52 PM Chapter01.fm Page 3 Monday, August 23, 2004

More information

Hello! ios Development

Hello! ios Development SAMPLE CHAPTER Hello! ios Development by Lou Franco Eitan Mendelowitz Chapter 1 Copyright 2013 Manning Publications Brief contents PART 1 HELLO! IPHONE 1 1 Hello! iphone 3 2 Thinking like an iphone developer

More information

XML. Jonathan Geisler. April 18, 2008

XML. Jonathan Geisler. April 18, 2008 April 18, 2008 What is? IS... What is? IS... Text (portable) What is? IS... Text (portable) Markup (human readable) What is? IS... Text (portable) Markup (human readable) Extensible (valuable for future)

More information

WordPress Tutorial for Beginners with Step by Step PDF by Stratosphere Digital

WordPress Tutorial for Beginners with Step by Step PDF by Stratosphere Digital WordPress Tutorial for Beginners with Step by Step PDF by Stratosphere Digital This WordPress tutorial for beginners (find the PDF at the bottom of this post) will quickly introduce you to every core WordPress

More information

Taking Advantage of ADSI

Taking Advantage of ADSI Taking Advantage of ADSI Active Directory Service Interfaces (ADSI), is a COM-based set of interfaces that allow you to interact and manipulate directory service interfaces. OK, now in English that means

More information

Security. 1 Introduction. Alex S. 1.1 Authentication

Security. 1 Introduction. Alex S. 1.1 Authentication Security Alex S. 1 Introduction Security is one of the most important topics in the IT field. Without some degree of security, we wouldn t have the Internet, e-commerce, ATM machines, emails, etc. A lot

More information

Furl Furled Furling. Social on-line book marking for the masses. Jim Wenzloff Blog:

Furl Furled Furling. Social on-line book marking for the masses. Jim Wenzloff Blog: Furl Furled Furling Social on-line book marking for the masses. Jim Wenzloff jwenzloff@misd.net Blog: http://www.visitmyclass.com/blog/wenzloff February 7, 2005 This work is licensed under a Creative Commons

More information

Views in SQL Server 2000

Views in SQL Server 2000 Views in SQL Server 2000 By: Kristofer Gafvert Copyright 2003 Kristofer Gafvert 1 Copyright Information Copyright 2003 Kristofer Gafvert (kgafvert@ilopia.com). No part of this publication may be transmitted,

More information

Background. $VENDOR wasn t sure either, but they were pretty sure it wasn t their code.

Background. $VENDOR wasn t sure either, but they were pretty sure it wasn t their code. Background Patient A got in touch because they were having performance pain with $VENDOR s applications. Patient A wasn t sure if the problem was hardware, their configuration, or something in $VENDOR

More information

CMPT 354 Database Systems I

CMPT 354 Database Systems I CMPT 354 Database Systems I Chapter 8 Database Application Programming Introduction Executing SQL queries: Interactive SQL interface uncommon. Application written in a host language with SQL abstraction

More information

Database Systems: Design, Implementation, and Management Tenth Edition. Chapter 14 Database Connectivity and Web Technologies

Database Systems: Design, Implementation, and Management Tenth Edition. Chapter 14 Database Connectivity and Web Technologies Database Systems: Design, Implementation, and Management Tenth Edition Chapter 14 Database Connectivity and Web Technologies Database Connectivity Mechanisms by which application programs connect and communicate

More information

WebRAD: Building Database Applications on the Web with Visual FoxPro and Web Connection

WebRAD: Building Database Applications on the Web with Visual FoxPro and Web Connection WebRAD: Building Database Applications on the Web with Visual FoxPro and Web Connection Harold Chattaway Randy Pearson Whil Hentzen Hentzenwerke Publishing Published by: Hentzenwerke Publishing 980 East

More information

Setting up ODBC, Part 2 Robert Abram

Setting up ODBC, Part 2 Robert Abram Seite 1 von 7 Issue Date: FoxTalk October 2000 Setting up ODBC, Part 2 Robert Abram rob_abram@smartfella.com In this second article of a series about setting up ODBC connections programmaticly through

More information

Outlook is easier to use than you might think; it also does a lot more than. Fundamental Features: How Did You Ever Do without Outlook?

Outlook is easier to use than you might think; it also does a lot more than. Fundamental Features: How Did You Ever Do without Outlook? 04 537598 Ch01.qxd 9/2/03 9:46 AM Page 11 Chapter 1 Fundamental Features: How Did You Ever Do without Outlook? In This Chapter Reading e-mail Answering e-mail Creating new e-mail Entering an appointment

More information

Understanding Business Objects, Part 3

Understanding Business Objects, Part 3 Understanding Business Objects, Part 3 Once you have business objects, you need to connect them to the user interface. Plus changing the application is easier than when business logic and UI code are mingled

More information

You can use PIVOT even when you don t know the list of possible values, and you can unpivot in order to normalize unnormalized data.

You can use PIVOT even when you don t know the list of possible values, and you can unpivot in order to normalize unnormalized data. More on PIVOT You can use PIVOT even when you don t know the list of possible values, and you can unpivot in order to normalize unnormalized data. Tamar E. Granor, Ph.D. In my last article, I explored

More information

MAPLOGIC CORPORATION. GIS Software Solutions. Getting Started. With MapLogic Layout Manager

MAPLOGIC CORPORATION. GIS Software Solutions. Getting Started. With MapLogic Layout Manager MAPLOGIC CORPORATION GIS Software Solutions Getting Started With MapLogic Layout Manager Getting Started with MapLogic Layout Manager 2008 MapLogic Corporation All Rights Reserved 330 West Canton Ave.,

More information

Intro. Scheme Basics. scm> 5 5. scm>

Intro. Scheme Basics. scm> 5 5. scm> Intro Let s take some time to talk about LISP. It stands for LISt Processing a way of coding using only lists! It sounds pretty radical, and it is. There are lots of cool things to know about LISP; if

More information

Workshop. Import Workshop

Workshop. Import Workshop Import Overview This workshop will help participants understand the tools and techniques used in importing a variety of different types of data. It will also showcase a couple of the new import features

More information

Welcome to another episode of Getting the Most. Out of IBM U2. I'm Kenny Brunel, and I'm your host for

Welcome to another episode of Getting the Most. Out of IBM U2. I'm Kenny Brunel, and I'm your host for Welcome to another episode of Getting the Most Out of IBM U2. I'm Kenny Brunel, and I'm your host for today's episode, and today we're going to talk about IBM U2's latest technology, U2.NET. First of all,

More information