Database Advances Define VB6New data-access features might change the wayyou write applications. by Andrew J. Brust
Reprinted with permission from Visual Basic Programmer's Journal, 10/98, Volume 8, Issue 12, Copyright 1998, Fawcette Technical Publications, Palo Alto, CA, USA. To subscribe, call 1-800-848-5523, 650-833-7100, visit www.vbpj.com, or visit The Development Exchange at www.devx.com Visual Basic 1.0 didn't have built-in data access (unless you count low-level file I/O). Even so, with the aid of third-party tools, a large percentage of VB1-authored programs were database applications. Even then, developers who needed to write custom database applications embraced VB. And each successive version of Visual Basic has upped the database ante: VB2 introduced ODBC data access, VB3 gave us the Jet Database Engine 1.1, and the Visual Basic Compatibility Layer (VBCL) let VB3 work with Jet 2.0. In the world of 32-bit tools, VB4 provided us with non-Jet data access in the form of Remote Data Objects (RDO), and VB5 gave us the UserConnection Designer, the T-SQL Debugger, RDO 2.0, and the Visual Database Tools (VDT), which ran under the Developer Studio IDE. Microsoft's release of ActiveX Data Objects (ADO) 1.0 and 1.5 gave VB developers even more data-access options.
I have to warn you before you start looking at these features that there are a lot of them, and some of them are (at first) hard to grasp. Along with the continued presence of all the older technologiesData Access Objects (DAO), RDO, and their associated control-binding facilitiesVB now offers a dizzying array of different ways to do the same thing. You absolutely must set aside some time to soak up all these techniques and technologies. And from now on, before you start developing an application, you'll need to consider carefully which data-access technique is most appropriate for your needs. VB's new database features fall into four main areas: the underpinning of ADO 2.0 and OLE DB throughout VB's database facilities and data access in general, including the existence of the new ADO Data control; integration of the Visual Database Tools (VDT) into VB; the Data Environment Designer and its use as a data source in applications; and data-aware classes. I'll cover all these and some miscellaneous, related features in as much detail as is possible in one article. To acquire full appreciation of these features, though, requires more study. The other Database Design column authors and I will keep busy for some time covering many of VB6's database features.
ADO Everywhere For simple data-bound operations, VB6 offers a new ADO-compatible data control. The various standard and data-bound controls included with VB are all compatible with this control. As with the Remote Data Control, you must explicitly add the ADO Data Control (ADODC) to your project (in the Components dialog). Add an instance of the ADODC to a form, user control, user document, or property page, and you'll see the properties and control itself work in a similar fashion to the intrinsic (Jet) Data Control and the Remote Data Control present in previous versions of VB. One welcome ADODC feature is the interactive property sheet that comes up when you double-click on ConnectionString in the Properties window. The property sheet lets you specify a Data Link File (the OLE DB equivalent of an ODBC file-based data source), an ODBC source (a DSN-less connection string or a new or existing user, system, or file-based ODBC data source), or an OLE DB connection string. If you choose the last of these three, you can enter the connection string manually or click on the Build button to bring up the Data Link Properties sheet. This tabbed dialog builds OLE DB connection strings interactively by letting you select a provider and specify user ID, password, database/catalog, and other options.
VDT in the IDE
Once you have a connection in your data window, you'll notice it becomes an expandable treeview node. Open it and you'll see a number of child branches. The exact branches you'll see depends on the database you're connected to. For all databases, you'll see branches for tables and views (though the latter might be empty). For SQL Server, you'll also see branches for database diagrams and stored procedures. For Oracle, you'll see all of these, plus branches for synonyms and functions. You can expand any of these branches to see the actual objects in your databases, then expand any of those objects to see the fields and/or parameters within them. By the way, triggers appear as leaf nodes underneath their corresponding table branches. Any branch can display properties in a floating property sheet by a right-click context menu selection. What can you do besides browse the objects in your database? You can double-click (or right-click and choose Open from the popup menu) on any table or view in the Data View window to see and, for certain databases (Jet not among them), change the data in a datasheet window. You can also use VDT's query editor to refine your query and make the underlying SQL more sophisticated than the default Select * from object that appears when you open it. VDT offers many other features, which I'll describe later, but most of them are enabled only for SQL Server and Oracle. Right-clicking on the connection's immediate child branches provides context menus that let you specify whether you wish to view system objects, add corresponding objects, or define an ownership filter for displayed objects. Clicking on an individual table, view, stored procedure, synonym, or function lets you open, design, or delete the selected object or add a new object of the same type; for tables, you also have the option of adding a trigger. Stored procedures offer a Debug option, and stored procedures and triggers have a Save as Text option that saves the corresponding script to an ASCII file. When you open a table, view, synonym, or data project query, you are placed in the Query Editor (see Figure 2). This tool allows you to see any combination of Design, Grid, SQL, and Results views of your query at any time. You specify which panes you want to see through Show Panes under the View menu. The Query menu lets you control many other aspects of the query. You can change your query, but unless you create or modify a View, this has no effect on the database itself. Except in the case of Data Environment commands and Views, once you close the Query Designer window, you lose your changes. If you need to add a table to your query, don't look for a menu option or toolbar button to provide you with a pick list of tables. Instead, you must add the table's name to your SQL statement or drag and drop a table from the Data View window to the Design pane.
When you choose the Design option for a stored procedure, trigger, or function, the corresponding object opens in an MDI child window-based text editor with color-coded language keywords. When you add a new stored procedure, trigger, or function, the same editor is invoked and already contains "stub" code, appropriate to the object type selected and the database in use. When you choose the Design option for a table, a grid appears and allows you to add and delete table columns (fields), define their attributes, and set primary keys. Right-clicking on this grid and choosing Properties brings up a property sheet that allows you to define constraints, relationships, and indexes. If you find doing design work on one table at a time somewhat tedious, you can use database diagrams. A database diagram, in its simplest definition, is a blank canvas upon which you can view the design grids for some or all of the tables in your database and the key-based relationships between them. You can view several tables at once, define relationships between them, and modify their structures.
For SQL Server, you can click on the stored procedure editor's Debug toolbar button (or right-click on the stored procedure in the Data View window and choose Debug) to trace the code through the T-SQL Debugger, which will also show you the values and/or resultset(s) returned. VB automatically prompts you for any input parameter values the procedure requires.
Environmental Data
Don't like bound controls? That's okay: VBA code can access Data Environment commands at run time. Furthermore, the Data Environment Designer lets you easily build hierarchical commands, which are extremely useful for parent/child-type data relationships. While this is possible without the Data Environment, the syntax is complex.
Let's explore how to use these features by building a hierarchical command object on the authors and titles tables in the SQL Server Pubs database. First, create a new Standard EXE project in VB6 (although a Data Project template is available, Standard EXE is all we need here). Next, choose Add Data Environment from the Project menu (or by right-clicking on the Project window) and you'll be placed directly in the designer window for the DataEnvironment object. Using the Properties window, change the object's name to envMain, then change the name of the default connection within it from Connection1 to conPubs. Next, right-click on conPubs and choose Properties to open the Data Link Properties sheet. In the Provider tab, choose Microsoft OLE DB Provider for SQL Server, and click on the Connection tab. Now enter the server name, user ID, password, and "pubs" for the database name. Click on the Test Connection button to make certain everything is configured properly; if it is, you should see a message box reading "Test connection succeeded." After you're done, click on OK.
Next, create a Command object that selects all records from the authors table. To do so, right-click on conPubs and choose Add Command from the context menu. In the Properties window, change the name from the default Command1 to "comAuthors", then right-click on it and choose Properties to bring up the Command Properties sheet. In the General tab, select the SQL Statement radio button, and click on the SQL Builder command button, which opens the VDT Query Editor. Use the View | Show Panes menu option to make sure the Design, Grid, SQL, and Results panes are all showing. Next, open VB's Data View window (if it is not already open) using the Data View Window toolbar button or the View | Data View window menu option. Open the Data Environment Connections branch, then open the conPubs and Tables branches. Find the authors branch and drag it to the design area of the Query Editor. Now select the check boxes next to All Columns, au_lname, and au_fname. In the Grid pane, clear the Output check boxes next to au_lname and au_fname and for those same fields, select Ascending in the Sort Type column combo boxes. If you did everything right, you should see this query in the SQL pane:
Choose the Run option from the Query menu to see the results of your query in the Results pane of the Query Designer. If the results look correct, close the MDI child window that's hosting the Query Designer. When VB prompts you with the message "Save changes to the query 'comAuthors'?", click on Yes. You'll notice that the icon next to comAuthors has changed to the SQL icon and that a plus sign indicates the branch can be opened. Open the branch, and you'll see child branches for each field returned by the command.
One important feature of the Command object property sheet is its ability to let you specify a stored procedure in the General tab, and to supply parameter input values in the Parameters tab. In my testing, I found that commands built on ODBC-based connections provide you with a populated list of available stored procedures and then automatically fetch all the parameter names for which you must supply input values. Anyone who has written the code necessary to bind an ADO command and its Parameters collection to a stored procedure will immediately appreciate this feature. Also of interest are the Grouping and Aggregates tabs, which handily generate SQL GROUP BY clauses. The Advanced tab lets you control the type of ADO Recordset the Command object creates and how the command is executed.
Navigate the Hierarchy
When you're done, go back into the comTitlesAuthors property sheet and click on the Relation tab. The parent command combo box should already be displaying comAuthors; if it's not, then configure it to do so. Next, make sure the Parent Fields and Child Fields/Parameters combo boxes both display au_id; click on the Add command button, then click on OK.
Congratulations! You've created your first hierarchical command object. To see the SQL that the Data Environment designer built for you, right-click on comAuthors and choose Hierarchy Info... from the context menu. A popup window displays the SQL-like SHAPE command used to generate the recordset, assuming the View Shape Command radio button is selected.
To enjoy the fruits of your labor, open Form1 and set its width to 5800. Open envMain and choose Window | Tile Vertically from VB6's menu. Drag the comAuthors object from the Data Environment window to the top center of Form1. When you let go of the mouse, you'll see something impressive: bound controls with accompanying labels for each field in the parent command, and a bound MSHFlexGrid (the reference to which was added to your project automatically) to display the child command's resultset. This is ready to run; click on the Start toolbar button to see for yourself.
New Data-Bound Properties
Here are some more quick facts: The DataFormat property lets you tell VB how to format your data, reducing the need to use third-party masked-edit controls. The CausesValidation property, when set to True, causes the control's Validate event to fire when the control loses focus. In the Validate event procedure, if the data fails your validation test, you can set the Cancel parameter to True, in which case the control retains focus automatically. This is cleaner than writing code in the LostFocus event. You can set the CausesValidation property to True or False at run time to enable and disable that code dynamically.
One more trick before you move on: Add a new form to the project, place a single MSHFlexGrid on it, and size the grid to fill the form's client area. Set the DataSource property of the grid to envMain, and the DataMember property to comAuthors. Change your startup object to this new form and run the application. You might need to scroll right to appreciate fully what this control does: It displays the titles information to the right (and below, in the case of multiple titles) of the author information and provides Expand/Collapse buttons to the left of each author. The combination of the Hierarchical Grid control and ADO hierarchical recordsets is powerful indeed.
Roll Your Own
Feel free to add similar buttons and code for MoveFirst, MovePrevious, and MoveLast functionality. Now run your code (set the startup object back to Form1 first) and see how well it works. If you're comfortable with that, you can work with rscomAuthors without using bound controls at all. Just make sure you call its Open method (with no parameters) before trying to read or manipulate it. What if you want programmatic access to fields in the comAuthors' child recordset (the one created by comTitlesAuthors)? It's not difficult. Simply access the child recordset as if it were a field of the parent recordset. Use this line of code to display in a message box the first title belonging to the current author:
Note that the Value property at the end of the line (on Fields("Title")) is optional, but explicitly referencing the Value property of Fields("comTitlesAuthors") is absolutely required.
You might have noticed a rule here: The recordset opened by a command can be addressed as a property of the DataEnvironment object where the property's name is "rs" concatenated with the name of the command object ("rs" + "comAuthors" = "rscomAuthors"). You can also refer to the recordset through the DataEnvironment object's Recordsets collection, which is 1-based. Addressed in this fashion, our recordset would be envMain.Recordsets("comAuthors") or envMain.Recordsets(1).
By the way, if you're really brave, you can create hierarchical recordsets on your own. The SHAPE statement for the recordset in this example is the one that was displayed in the Hierarchy Info popup; it uses the new SHAPE command to create the hierarchical recordset. In order to use the SHAPE command, you must use a special OLE DB service provider named MSDataShape.1 in combination with the data provider normally required (SQLOLEDB.1 in the case of SQL Server). If you interrogate the value of conPubs' Connection String (envMain.conPubs.ConnectionString), here's what you'll see:
The Provider and Data Provider clauses reference the two OLE DB providers mentioned before. The Data Source clause selects TEST as the SQL Server (this is similar to the SERVER connect string clause the SQL Server ODBC Driver uses); the Initial Catalog clause selects Pubs as the database (similar to the DATABASE connect string clause the SQL Server ODBC Driver uses). The Persist Security Info and Connect Timeout clauses are not strictly required.
Data-Aware Classes
The ActiveX components that make all this possible are called the Microsoft Data Binding Collection and the Microsoft Data Source Interfaces. To use "simple bound" data-aware classes, you must add (in the References dialog) the first of these two libraries to your project. You'll need the second one for "complex bound" operations (where your class provides several recordsets), and its clients use their DataMember property to pick one.
Next, add code required to create a Data Consumer class that binds to the authors table in the Pubs database, though for reasons of space, only one property will be implemented: FirstName (see Listing 1). The Class_Initialize procedure contains most of the magic code. A recordset is opened (it's private and inline SQL-based, but you could use a DataEnvironment recordset as well),
and the BindingCollection object's DataSource property is set to it. Next, use the Add method of the BindingCollection object to bind the class's FirstName property to the au_fname field of the recordset. This ensures that whenever the recordset is navigated, the Property Let FirstName procedure is always called and passed the current value of au_fname in its szNewValue parameter.
Next, implement clsAuthorSource, a Data Source class that wraps the authors table (download Listing 2). I gave the class a Cycle method to navigate through the resultset. Create a form whose controls will bind (for reading and writing) to clsAuthorSource as their data source, along with a command button to call the Cycle method (download Listing 3). With a little work, the code in clsAuthorSource could be ported to a UserControl object, allowing the data binding to occur at design time instead of in code in the Form_Load. Remember, the recordset that clsAuthorSource returns doesn't have to come from a database; you could create a recordset in code and make anything look like a data table. In such a case, you're writing a data provider of sorts. Wish you could write a full-fledged OLE DB Simple Provider that any OLE DB client could use? Well, I can't cover it in depth in this article, but rest assured: It's documented and doable. The code in Listings 2 and 3 works only if you set clsAuthorSource's DataSourceBehavior property to vbDataSource.
Time is short, so I can only briefly tell you about VB6's new built-in Data Report Designer. The good thing about this Access Report Writer-like tool is that it's small and compatible with VB6's data-binding plumbing, including the ability to drag and drop Data Environment commands and fields. Implemented as an ActiveX Designer, it's ready for you to insert VB code into any of its nine event procedures. On the other hand, its reporting capabilities are crude compared to those of its competitors. Given that Seagate/Crystal and Data Dynamics both have ActiveX Designer-based reporting tools available, it might not be time to switch just yet.
That's about all I have space to cover. As you can see, VB6 brings a lot to the table (no pun intended) in terms of new features for writing robust n-tier database applications and server objects. Much thought has gone into simplifying developers' lives and facilitating their transition to ADO and OLE DB. Get your hands on VB6 as soon as you can, and set aside some time to experiment with all these new capabilities. After you understand them, they might bring fundamental changes to the way you write applications.
|