- •Using Your Sybex Electronic Book
- •Acknowledgments
- •Contents at a Glance
- •Introduction
- •Who Should Read This Book?
- •How About the Advanced Topics?
- •The Structure of the Book
- •How to Reach the Author
- •The Integrated Development Environment
- •The Start Page
- •Project Types
- •Your First VB Application
- •Making the Application More Robust
- •Making the Application More User-Friendly
- •The IDE Components
- •The IDE Menu
- •The Toolbox Window
- •The Solution Explorer
- •The Properties Window
- •The Output Window
- •The Command Window
- •The Task List Window
- •Environment Options
- •A Few Common Properties
- •A Few Common Events
- •A Few Common Methods
- •Building a Console Application
- •Summary
- •Building a Loan Calculator
- •How the Loan Application Works
- •Designing the User Interface
- •Programming the Loan Application
- •Validating the Data
- •Building a Math Calculator
- •Designing the User Interface
- •Programming the MathCalculator App
- •Adding More Features
- •Exception Handling
- •Taking the LoanCalculator to the Web
- •Working with Multiple Forms
- •Working with Multiple Projects
- •Executable Files
- •Distributing an Application
- •VB.NET at Work: Creating a Windows Installer
- •Finishing the Windows Installer
- •Running the Windows Installer
- •Verifying the Installation
- •Summary
- •Variables
- •Declaring Variables
- •Types of Variables
- •Converting Variable Types
- •User-Defined Data Types
- •Examining Variable Types
- •Why Declare Variables?
- •A Variable’s Scope
- •The Lifetime of a Variable
- •Constants
- •Arrays
- •Declaring Arrays
- •Initializing Arrays
- •Array Limits
- •Multidimensional Arrays
- •Dynamic Arrays
- •Arrays of Arrays
- •Variables as Objects
- •So, What’s an Object?
- •Formatting Numbers
- •Formatting Dates
- •Flow-Control Statements
- •Test Structures
- •Loop Structures
- •Nested Control Structures
- •The Exit Statement
- •Summary
- •Modular Coding
- •Subroutines
- •Functions
- •Arguments
- •Argument-Passing Mechanisms
- •Event-Handler Arguments
- •Passing an Unknown Number of Arguments
- •Named Arguments
- •More Types of Function Return Values
- •Overloading Functions
- •Summary
- •The Appearance of Forms
- •Properties of the Form Control
- •Placing Controls on Forms
- •Setting the TabOrder
- •VB.NET at Work: The Contacts Project
- •Anchoring and Docking
- •Loading and Showing Forms
- •The Startup Form
- •Controlling One Form from within Another
- •Forms vs. Dialog Boxes
- •VB.NET at Work: The MultipleForms Project
- •Designing Menus
- •The Menu Editor
- •Manipulating Menus at Runtime
- •Building Dynamic Forms at Runtime
- •The Form.Controls Collection
- •VB.NET at Work: The DynamicForm Project
- •Creating Event Handlers at Runtime
- •Summary
- •The TextBox Control
- •Basic Properties
- •Text-Manipulation Properties
- •Text-Selection Properties
- •Text-Selection Methods
- •Undoing Edits
- •VB.NET at Work: The TextPad Project
- •Capturing Keystrokes
- •The ListBox, CheckedListBox, and ComboBox Controls
- •Basic Properties
- •The Items Collection
- •VB.NET at Work: The ListDemo Project
- •Searching
- •The ComboBox Control
- •The ScrollBar and TrackBar Controls
- •The ScrollBar Control
- •The TrackBar Control
- •Summary
- •The Common Dialog Controls
- •Using the Common Dialog Controls
- •The Color Dialog Box
- •The Font Dialog Box
- •The Open and Save As Dialog Boxes
- •The Print Dialog Box
- •The RichTextBox Control
- •The RTF Language
- •Methods
- •Advanced Editing Features
- •Cutting and Pasting
- •Searching in a RichTextBox Control
- •Formatting URLs
- •VB.NET at Work: The RTFPad Project
- •Summary
- •What Is a Class?
- •Building the Minimal Class
- •Adding Code to the Minimal Class
- •Property Procedures
- •Customizing Default Members
- •Custom Enumerations
- •Using the SimpleClass in Other Projects
- •Firing Events
- •Shared Properties
- •Parsing a Filename String
- •Reusing the StringTools Class
- •Encapsulation and Abstraction
- •Inheritance
- •Inheriting Existing Classes
- •Polymorphism
- •The Shape Class
- •Object Constructors and Destructors
- •Instance and Shared Methods
- •Who Can Inherit What?
- •Parent Class Keywords
- •Derived Class Keyword
- •Parent Class Member Keywords
- •Derived Class Member Keyword
- •MyBase and MyClass
- •Summary
- •On Designing Windows Controls
- •Enhancing Existing Controls
- •Building the FocusedTextBox Control
- •Building Compound Controls
- •VB.NET at Work: The ColorEdit Control
- •VB.NET at Work: The Label3D Control
- •Raising Events
- •Using the Custom Control in Other Projects
- •VB.NET at Work: The Alarm Control
- •Designing Irregularly Shaped Controls
- •Designing Owner-Drawn Menus
- •Designing Owner-Drawn ListBox Controls
- •Using ActiveX Controls
- •Summary
- •Programming Word
- •Objects That Represent Text
- •The Documents Collection and the Document Object
- •Spell-Checking Documents
- •Programming Excel
- •The Worksheets Collection and the Worksheet Object
- •The Range Object
- •Using Excel as a Math Parser
- •Programming Outlook
- •Retrieving Information
- •Recursive Scanning of the Contacts Folder
- •Summary
- •Advanced Array Topics
- •Sorting Arrays
- •Searching Arrays
- •Other Array Operations
- •Array Limitations
- •The ArrayList Collection
- •Creating an ArrayList
- •Adding and Removing Items
- •The HashTable Collection
- •VB.NET at Work: The WordFrequencies Project
- •The SortedList Class
- •The IEnumerator and IComparer Interfaces
- •Enumerating Collections
- •Custom Sorting
- •Custom Sorting of a SortedList
- •The Serialization Class
- •Serializing Individual Objects
- •Serializing a Collection
- •Deserializing Objects
- •Summary
- •Handling Strings and Characters
- •The Char Class
- •The String Class
- •The StringBuilder Class
- •VB.NET at Work: The StringReversal Project
- •VB.NET at Work: The CountWords Project
- •Handling Dates
- •The DateTime Class
- •The TimeSpan Class
- •VB.NET at Work: Timing Operations
- •Summary
- •Accessing Folders and Files
- •The Directory Class
- •The File Class
- •The DirectoryInfo Class
- •The FileInfo Class
- •The Path Class
- •VB.NET at Work: The CustomExplorer Project
- •Accessing Files
- •The FileStream Object
- •The StreamWriter Object
- •The StreamReader Object
- •Sending Data to a File
- •The BinaryWriter Object
- •The BinaryReader Object
- •VB.NET at Work: The RecordSave Project
- •The FileSystemWatcher Component
- •Properties
- •Events
- •VB.NET at Work: The FileSystemWatcher Project
- •Summary
- •Displaying Images
- •The Image Object
- •Exchanging Images through the Clipboard
- •Drawing with GDI+
- •The Basic Drawing Objects
- •Drawing Shapes
- •Drawing Methods
- •Gradients
- •Coordinate Transformations
- •Specifying Transformations
- •VB.NET at Work: Plotting Functions
- •Bitmaps
- •Specifying Colors
- •Defining Colors
- •Processing Bitmaps
- •Summary
- •The Printing Objects
- •PrintDocument
- •PrintDialog
- •PageSetupDialog
- •PrintPreviewDialog
- •PrintPreviewControl
- •Printer and Page Properties
- •Page Geometry
- •Printing Examples
- •Printing Tabular Data
- •Printing Plain Text
- •Printing Bitmaps
- •Using the PrintPreviewControl
- •Summary
- •Examining the Advanced Controls
- •How Tree Structures Work
- •The ImageList Control
- •The TreeView Control
- •Adding New Items at Design Time
- •Adding New Items at Runtime
- •Assigning Images to Nodes
- •Scanning the TreeView Control
- •The ListView Control
- •The Columns Collection
- •The ListItem Object
- •The Items Collection
- •The SubItems Collection
- •Summary
- •Types of Errors
- •Design-Time Errors
- •Runtime Errors
- •Logic Errors
- •Exceptions and Structured Exception Handling
- •Studying an Exception
- •Getting a Handle on this Exception
- •Finally (!)
- •Customizing Exception Handling
- •Throwing Your Own Exceptions
- •Debugging
- •Breakpoints
- •Stepping Through
- •The Local and Watch Windows
- •Summary
- •Basic Concepts
- •Recursion in Real Life
- •A Simple Example
- •Recursion by Mistake
- •Scanning Folders Recursively
- •Describing a Recursive Procedure
- •Translating the Description to Code
- •The Stack Mechanism
- •Stack Defined
- •Recursive Programming and the Stack
- •Passing Arguments through the Stack
- •Special Issues in Recursive Programming
- •Knowing When to Use Recursive Programming
- •Summary
- •MDI Applications: The Basics
- •Building an MDI Application
- •Built-In Capabilities of MDI Applications
- •Accessing Child Forms
- •Ending an MDI Application
- •A Scrollable PictureBox
- •Summary
- •What Is a Database?
- •Relational Databases
- •Exploring the Northwind Database
- •Exploring the Pubs Database
- •Understanding Relations
- •The Server Explorer
- •Working with Tables
- •Relationships, Indices, and Constraints
- •Structured Query Language
- •Executing SQL Statements
- •Selection Queries
- •Calculated Fields
- •SQL Joins
- •Action Queries
- •The Query Builder
- •The Query Builder Interface
- •SQL at Work: Calculating Sums
- •SQL at Work: Counting Rows
- •Limiting the Selection
- •Parameterized Queries
- •Calculated Columns
- •Specifying Left, Right, and Inner Joins
- •Stored Procedures
- •Summary
- •How About XML?
- •Creating a DataSet
- •The DataGrid Control
- •Data Binding
- •VB.NET at Work: The ViewEditCustomers Project
- •Binding Complex Controls
- •Programming the DataAdapter Object
- •The Command Objects
- •The Command and DataReader Objects
- •VB.NET at Work: The DataReader Project
- •VB.NET at Work: The StoredProcedure Project
- •Summary
- •The Structure of a DataSet
- •Navigating the Tables of a DataSet
- •Updating DataSets
- •The DataForm Wizard
- •Handling Identity Fields
- •Transactions
- •Performing Update Operations
- •Updating Tables Manually
- •Building and Using Custom DataSets
- •Summary
- •An HTML Primer
- •HTML Code Elements
- •Server-Client Interaction
- •The Structure of HTML Documents
- •URLs and Hyperlinks
- •The Basic HTML Tags
- •Inserting Graphics
- •Tables
- •Forms and Controls
- •Processing Requests on the Server
- •Building a Web Application
- •Interacting with a Web Application
- •Maintaining State
- •The Web Controls
- •The ASP.NET Objects
- •The Page Object
- •The Response Object
- •The Request Object
- •The Server Object
- •Using Cookies
- •Handling Multiple Forms in Web Applications
- •Summary
- •The Data-Bound Web Controls
- •Simple Data Binding
- •Binding to DataSets
- •Is It a Grid, or a Table?
- •Getting Orders on the Web
- •The Forms of the ProductSearch Application
- •Paging Large DataSets
- •Customizing the Appearance of the DataGrid Control
- •Programming the Select Button
- •Summary
- •How to Serve the Web
- •Building a Web Service
- •Consuming the Web Service
- •Maintaining State in Web Services
- •A Data-Driven Web Service
- •Consuming the Products Web Service in VB
- •Summary
928 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
To get the data into a DataSet, you must first establish a connection to the database. The Connection object allows you to specify the database you want to work with, and it’s one of the simpler objects of ADO.NET. Between the database and the DataSet there’s another object, the DataAdapter object. While the client application works with the data in the DataSet most of the time, every now and then it must exchange information with the database (query the database, or update it). The communication between the database and the DataSet takes place through the DataAdapter. This object knows how to update the database, as well as how to move data from the database and store them into the DataSet. As you will see, the DataAdapter object contains four commands for retrieving rows from the database, updating and deleting existing rows, and inserting new rows. These commands are SQL statements, and this is all the information the DataAdapter object needs to move data between the DataSet and the underlying data source.
The first advantage of the DataSet object is that it doesn’t care where its data came from, as long as there’s a DataAdapter object that can move data to and from the DataSet. As a result, you can create DataSets in code, or from an XML file. You can even save (or persist) a DataSet object to a disk file and write a database application without the database.
How About XML?
ADO.NET and XML go hand in hand, so where does XML come into the picture? XML is a method of representing structured data, and ADO.NET uses XML to pass data between the server and the client. Fortunately, you don’t have to write any XML code yourself, neither do you have to parse XML documents to retrieve the information. ADO.NET uses XML for its own purposes, and you can take advantage of it and write XML code, if you want. It is possible, for example, to create a new DataSet with XML statements and store your data there. This DataSet is totally independent of a database and resides in the client computer’s memory. When you’re done using it, you can store it to a file and retrieve it from there later. In effect, this is a mechanism to create your own data store (it’s not a database, of course, but you can have related tables), without the overhead of setting up a SQL Server database.
Later in this chapter, you will see this technique in action. XML is an interesting technology, but it’s not required for learning the basics of programming with ADO.NET. After mastering simpler topics such as data binding and programming DataSets, you can explore XML on your own.
Creating a DataSet
We’ll start our exploration of database programming by creating a DataSet. In this chapter, you’ll see how to create a DataSet with visual tools and how to display its data on a grid. In the following chapter, you’ll learn how to create DataSets programmatically, but the visual tools are much simpler, and in most cases there’s no reason to write code to connect to a database and populate a DataSet.
To create a new database application, start a new project as usual. When the project’s form appears, open the Server Explorer and expand one of the databases. Select the Northwind database and expand its icon to see the objects of the database. In the Tables section, select the Customers table, drag it with the mouse, and drop it on the form. VB will add two new objects in the Components tray: SqlConnection1 and SqlDataAdapter1.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
CREATING A DATASET 929
The first object, SqlConnection1, is the application’s connection to the database. This object contains all the information needed to connect to the database. If you look at its properties, you will see that its ConnectionString property is:
data source=PowerToolkit;initial catalog=Northwind;integrated security=SSPI; persist security info=False;workstation id=POWERTOOLKIT;packet size=4096
SqlDataAdapter1 is the channel between your application and the database. The DataSet doesn’t know anything about the database—it’s not its job to know about the database. The application can request the data through the DataAdapter object, process them and then rely on the DataAdapter to update the database.
If you look up the properties of the SqlDataAdapter1 object (Figure 21.1), you’ll see that it has
a SelectCommand property, which is a Command object that retrieves the data from the table. The SelectCommand object has a property called CommandText, which is a SELECT SQL statement:
SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax FROM dbo.Customers
Figure 21.1
The properties of the SqlDataAdapter object
This statement was generated automatically when you dropped the Customers table on the form. VB picked up the information from the table’s structure in the database and create a SELECT statement to retrieve all the columns of all rows.
If you select the SelectCommand item in the Properties window and then click the button with the ellipsis that appears next to the item’s setting, the Query Builder window will pop up and you can edit the SELECT statement (to exclude a few columns, or specify selection criteria to limit the number of rows returned by the query). You can also edit the SELECT statement by selecting the SqlDataAdapter1 object on the designer and clicking the Configure Data Adapter command at the bottom of the Properties window.
The SqlDataAdapter1 also has an InsertCommand property, which is shown next:
INSERT INTO dbo.Customers(CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax)
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
930 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, @Region, @PostalCode, @Country, @Phone, @Fax);
SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax FROM dbo.Customers
WHERE (CustomerID = @Select_CustomerID)
Any string that starts with the @ symbol is a variable. The DataAdapter sets the values of all these variables to the values of the new row to be inserted and then executes the InsertCommand against the database. The INSERT statement will add a new row to the Customers table. The SELECT statement following the INSERT statement selects the newly added row from the table and returns it to the application. There are two more commands in the SqlDataAdapter object, UpdateCommand and DeleteCommand. These two commands update a row in the Customers table and delete a row, respectively. We’ll return to the DataAdapter object and look at its properties, as well as how to set it up manually, later in this chapter. For now, keep in mind that the action of adding a table to a form creates and configures a DataAdapter object.
Note If you’re working with an Access database, you’ll follow the same steps, but the objects will have different names. The Connection object’s default name will be OleDbConnection, and the DataAdapter object’s default name will be OleDbDataAdapter. The SQL statements that move data in and out of an Access database may use a slightly different syntax, but they’re equally simple statements.
So far, you’ve created and configured a DataAdapter object that knows how to access the database and retrieve the desired data. The next step is to tell the DataAdapter where to leave the data it retrieves from the database, and where the changes reside, so that it can update the database. This is the DataSet object. Select the DataAdapter object on the designer and locate the Generate Dataset link at the bottom of the Properties window. Alternatively, you can open the Data menu and select the Generate Dataset command. (The Data menu isn’t available unless the form is visible.) You will see the Generate Dataset dialog box, which is shown in Figure 21.2. This dialog box proposes to create a new DataSet object, named DataSet1, that will contain the table Customers. As you will see in the following example, a DataSet may contain multiple tables—that’s why you’re given the option to select the table(s) you want to add to the DataSet. Click OK to create the new DataSet object. In the following examples we’ll use more descriptive names, but for this introductory example I’m going to use the default names.
Two new items will be added to the project: the DataSet1.xsd item in the Solution Explorer and the DataSet11 object on the design surface. DataSet1 is a class that describes the structure of the data you’ll retrieve from the database. DataSet11 is an instance of this class—this is where the data will be stored.
Let’s see what we have done so far. Open the Data menu and select Preview Data. Alternatively, you can right-click the DataAdapter’s icon in the Components tray and select Preview Data. This command will open the Data Adapter Preview window, which is shown in Figure 21.3. When the Data Adapter Preview dialog box comes up, it will be initially empty. Click the Fill Dataset button to execute the query, populate the DataSet, and preview the Customers table. You can see that the query retrieved the desired data from the database. If the DataSet contains multiple tables, you’ll have to select the name of the table you want to preview in the Data Tables list, and its rows will appear in the preview pane.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
CREATING A DATASET 931
Figure 21.2
To generate a DataSet, specify its name and the tables you want to add to it.
Figure 21.3
Previewing the Customers table
The last step is to display the information to the user, and this is what we’ll do next. There are many ways to present the information on a front-end application, but we’ll start with a tool designed specifically for this purpose.
The DataGrid Control
One of the most common tasks in programming database applications is to present data to the users. We have created the DataSet that will hold our data, so we can now design an interface to present the data residing in the DataSet to the user. At design time, the DataSet is empty, of course. The DataSet object contains information about the structure of the table(s) it will hold at runtime, but no actual data.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
932 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
The primary control for displaying DataSets is the DataGrid control, which is similar to the grid you use to edit the rows of a table with SQL Server’s Enterprise Manager or even Access. The application you’ll develop in this section is called Customers (it’s available in this chapter’s folder on the companion CD), and its main form is shown in Figure 21.4. The DataGrid control can display not only single tables, but it also allows you to navigate through the rows of related tables. For example, you can display categories, select a category, and view the products under the selected category. Let’s start by displaying a single table on the DataGrid control.
Figure 21.4
Displaying the Customers table on a DataGrid control
Note By the way, the DataGrid control isn’t appropriate for all types of interfaces, and you shouldn’t give users free access to all the rows of a DataTable. However, it’s the best tool for visualizing the structure of a DataSet, and you will find it convenient at the beginning. As you learn more about the objects of ADO.NET and how to program them, you’ll start using the more traditional controls to build your interfaces. The DataGrid control, however, remains a powerful tool, especially for displaying DataSets.
Switch the project’s form and place a DataGrid control on it. To specify where the data will come from, you must set the control’s DataSource property to a DataSet. Locate the DataSource property in the Properties window and expand the list of possible settings for the property. You will see two settings: DataSet11 and DataSet11.Customers. If you set the DataSource property to a DataSet object, then you will have to specify which of the tables in the DataSet you want to display on the grid. Since our DataSet contains a single table, set the DataSource property to DataSet11.Customers. Alternatively, you can set the DataSource property to DataSet11 and the DataMember property Customers.
The names of all the fields will appear at the header of the DataGrid control. The data is still at the server; the control must be populated when the user requests it. That’s why you see only the field names and no data. By default, all columns have the same length. Clearly, you must customize the appearance of the DataGrid control. But first, let’s see what our data looks like on the control.
If you run the application now, you won’t see any data on the control. The DataSet must be populated explicitly by calling the Fill method of the associated DataAdapter. Place a button on the form, name it Load Data, and insert the following code in its Click event handler:
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
DataSet11.Clear()
SqlDataAdapter1.Fill(DataSet11, “Customers”)
End Sub
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
CREATING A DATASET 933
The first statement clears the current contents of the DataSet11 object. The Fill method of the DataAdapter accepts as arguments the names of a DataSet and of a table in the DataSet and populates the specified table. You can omit the second argument if the DataSet contains a single table.
Run the application, click the Load Data button, and the grid will be populated with the rows of the Customers table. You can edit the fields on the grid, but the changes are local to the DataSet and they’re not automatically submitted to the database. To update the underlying tables, you must explicitly call the Update method of the DataAdapter object. The Update method accepts two arguments, just like the Fill method. You specify the names of the DataSet and of the table to be updated. Enter the following statement behind the Update Table button:
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
SqlDataAdapter1.Update(DataSet11, “Customers”)
End Sub
Updating the rows in the underlying tables is not a trivial task, and it takes much more code than a single call to the Update method. For this example, we’ll assume that you’re updating simple tables and no other users are accessing the same tables at the same time. A more practical, robust approach requires quite a bit of code, and we’ll discuss it in the following chapter. The Update method updates one row at a time, and it stops as soon as it encounters an error. If the first edited row can’t be written to the underlying table, the DataAdapter object will not attempt to update the remaining edited rows. To continue the update process even if one or more rows fail, set the DataAdapter object’s ContinueUpdateOnError to True.
Customizing the DataGrid Control
The default appearance of the DataGrid control is rather blunt, and you’ll always have to customize it. The simplest customization tool for the DataGrid control is the AutoFormat command. Rightclick the DataGrid control and, from the context menu, select AutoFormat. On the dialog box that appears, you will see a list of available styles, such as Professional, Simple, and Classic. You can select each style on the dialog box to preview it and apply any style to the control by selecting it with the mouse and clicking OK to close the dialog box.
You can also customize each individual element of the control. Select the DataGrid control on the form and locate its TableStyles property in the Properties window. Click the button with the ellipsis and you will see the DataGridTableStyle Collection Editor, shown in Figure 21.5. The DataGrid control can display one or more tables, and each table can have its own style. Since our DataSet contains a single table, we must add a single DataGridTableStyle object to the collection. Click Add and the DataGridTableStyle1 object will be added to the collection. You can change its name too, but let’s leave the default for the first example.
Here you can set properties like the HeaderFont, the background color of the rows, as well as an alternate background color (the AlternatingBackColor value will be used for even-numbered rows). The DataGridTableStyle object won’t be automatically associated to any of the tables; you must set the name of the table explicitly with the MappingName property. Locate this property in the Properties section of the dialog box, expand the list of possible values (which are the names of the tables in the DataSet), and select the Customers table.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
934 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
Figure 21.5
Specifying the appearance of each table in the DataGrid control
Right above the MappingName property, you’ll see the GridColumnStyles property, which is also a collection. Click the button with the ellipsis, and you will see the GridColumnStyles Collection Editor, shown in Figure 21.6. This collection, which is initially empty, contains one member for each column, and each member determines the appearance of a different column in the parent table.
Figure 21.6
Specifying the appearance of each column on the DataGrid control
In the dialog box of Figure 21.6, you can set the widths, captions, and alignments of the individual columns. Don’t forget to set the MappingName property of each DataGridColumnStyle item to the appropriate field of the table. Any DataGridColumnStyle item that’s not mapped to a column will be ignored. The NullText property is a string that will appear in every field that’s Null.
Displaying Related Tables
In this section, we’ll build a project that involves two related tables, the Products and Categories tables. Most of the applications you’ll write will handle related rows from multiple tables, as isolated tables are quite rare. We’ll display the data on a DataGrid control again, but in a hierarchical way. The DataGrid allows you to display the rows of the parent table (in our case, the Categories table) and navigate to each parent row’s child rows. If a parent row has child rows, a plus sign is displayed in front of its name. You can click this symbol to view the related rows. At any point you can return
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
CREATING A DATASET 935
to the parent table, select another row, and view its child rows. The project you’ll build in this section is called RelatedTables, and you can find it on the CD. The project’s form is shown in Figure 21.7.
Figure 21.7
Displaying a table with related rows on the DataGrid control
The first step is to create a DataSet with two related tables. Start a new project, open the Server Explorer, and drop the Categories and Products tables of the Northwind database onto the form. VB will add automatically a Connection object to the form, as well as two DataAdapters. Since both DataAdapters see the same database, a single Connection object will suffice. Now you must configure the two DataAdapters. First, rename them to DACategories and DAProducts—no reason to work with names that differ in the last digit. To configure a DataAdapter object, select it with the mouse and click the Configure Data Adapter link at the bottom of the Properties window (or select the same command from the DataAdapter’s context menu). The default DataAdapter object is configured with a SQL statement that retrieves all columns and all rows of a table. In most cases you’ll have to edit this statement to retrieve a subset of a table.
To configure a DataAdapter, you can use the Data Adapter Configuration Wizard. In the following section, I will describe all the options offered by the wizard, and then we’ll use it to configure the
DACategories DataAdapter.
The Data Adapter Configuration Wizard
The Configure Data Adapter command will start the Data Adapter Configuration Wizard, and the first screen of the wizard is a welcome screen. Click Next to view the next one, which prompts you to select a Connection object. Accept the default connection, which was established when you dropped the tables on the form.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
936 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
Click Next to see the next screen of the wizard, which is the Choose A Query Type screen. Here you can specify the method that will be used to retrieve the data from the database. You can use SQL statements, use existing stored procedures, or create new stored procedures. When you’ll be working with large application or in a team environment, you will have to design the stored procedures first, and then you’ll start building database applications. The wizard can generate both the SQL statements and stored procedures needed to access the database. I suggest you familiarize yourself with SQL statements first and use stored procedures later. Let’s see how
each option works.
Select the SQL statements option and click Next. The Wizard will display the Generate The SQL Statements screen, where it will display a SELECT statement that retrieves all rows and all columns of the database. You can either edit the default SELECT statement or type a new statement. You can also click the Query Builder button to start Query Builder. Use this tool to visually specify the data you want to retrieve from one or more tables, as explained in Chapter 20. When you close the Query Builder, you’ll be returned to the Generate the SQL Statements screen and the new SELECT statement will appear on the this screen.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
CREATING A DATASET 937
By default, the wizard will create all the SQL statements for retrieving and editing the table (the INSERT, DELETE, and UPDATE statements). If you don’t plan to update the table from within the application—you only want to present data to the user—click the Advanced Options buttons. A dialog box will pop up where you can disable the generation of the UPDATE/INSERT/DELETE statements. On the same dialog box, you can set two more options to control how the statements will be generated.
The option Use Optimistic Concurrency applies to the UPDATE and DELETE statements. If you leave this option checked, the resulting statements will not modify any rows in the underlying table(s) in the database if these rows have been edited since your application read them. “Optimistic concurrency” means that we don’t anticipate multiple users editing the same row at the same time. If this happens rarely, then you can check the Optimistic Concurrency option. In the few cases that this happens, your application won’t be able to update the rows that have already been edited.
If you clear this option, the wizard will generate SELECT statements that update the underlying tables even if they have changed since they were read into the local DataSet. In this situation, the last user to commit his changes to the database wins.
The last option, Refresh The DataSet, generates SQL statements that update the database and then retrieve the rows they changed with a SQL statement. Check this option so that the application can immediately retrieve the new rows and display them on your form.
Click OK to close the Advanced Options dialog box and return to the wizard. Click the Next button to view the last screen that summarizes the results. Click Finish on this screen and the wizard will generate the SQL statements according to the options you specified on the wizard.
If you choose Create New Stored Procedures in the Choose A Query Type screen of the wizard, the next screen will display the SQL statement that retrieves all the data from the specified table. You can click the Query Builder to specify the query with visual tools. After specifying the SQL SELECT statement on which the stored procedures will be based, click Next to see the Create The Stored Procedures screen.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
938 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
On this screen, you can specify the names of the stored procedures for each action your application will perform against the database (select rows, insert new rows, editing existing rows, and delete rows). You can also specify whether the wizard should generate the stored procedures and add them to the database, or whether you want to write the stored procedures to the database yourself. You can click the Preview SQL Script button to see the stored procedures that the wizard will generate. You should let the wizard generate the stored procedures and edit them from within Server Explorer if you have to. Click Next to see the last screen of the wizard that summarizes the results.
If you choose Use Existing Stored Procedures in the Choose A Query Type screen of the wizard, the next screen will prompt you to select the four stored procedures for the actions you want to perform against the database. If a procedure requires parameters, you must specify the columns that contain the parameter values as well.
Configuring the DACategories DataAdapter
Let’s return to the RelatedTables project and configure the DACategories DataAdapter. The Categories table contains an Image field, which we won’t display on our form. Accept the default settings
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
CREATING A DATASET 939
on the Data Adapter Configuration Wizard, and change the SQL statement by removing the name of the Picture field. Here is the edited SQL statement you will see on the Generate the SQL Statements screen of the wizard:
SELECT CategoryID, CategoryName, Description
FROM dbo.Categories
Then configure the DAProducts DataAdapter by accepting all the defaults. Use the SQL statement generated by the wizard, which retrieves all the fields of the Products table (unless you want to omit a few columns). Once the two DataAdapters have been configured, you can generate the DataSet. Click the link Generate Dataset (or select the command Generate Dataset from the Data menu) and you will see the Generate Dataset dialog box (shown earlier in Figure 21.2). The wizard suggests that you create a DataSet with a single table. Check both table names and then change the default name of the DataSet to CategoriesProducts. You have just created a single DataSet with two tables.
When the CategoriesProducts.xsd file appears on the Solution Explorer, double-click it. The XSD file contains the schema of the two tables in the DataSet, but not the relationship between them. Even though the relationship between the two tables exists in the database, the wizard didn’t relate the tables in the DataSet to one another. You must do so by establishing a relationship manually. With the two tables on the designer’s surface, open the Toolbox. The Toolbox is a new one and contains the tools for editing XML schemas. Drag the Relation icon from the Toolbox and drop it on the Products table. The Edit Relation window, shown in Figure 21.8, will pop up. Here, you can specify the characteristics of the relationship between the two tables. Specifying a relationship between two tables in the DataSet is no different than specifying a relationship in the database. The Parent Element is the primary table, and the Child Element is the foreign table. After you have specified the primary and foreign tables, you must set the key fields of each table. Notice that the primary key can’t be changed: it’s always the parent table’s primary key. Finally, you can specify the rules for updating, deleting, and inserting rows. For now, leave the Default option in these boxes.
Figure 21.8
Establishing a relation between two tables in a DataSet
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
940 Chapter 21 BUILDING DATABASE APPLICATIONS WITH ADO.NET
Click the OK button to return to the XML Designer. The relationship you created is depicted with a line between the two tables as shown in Figure 21.9. You can click the diamond-shaped icon at the middle of this line and select Edit Relation to see a dialog box where you can modify the relation.
Figure 21.9
How a relation is depicted on the XML Designer
If you click the XML tab at the bottom of the Designer, you will see the XML description of the relationship:
<xsd:unique name=”Constraint1” msdata:PrimaryKey=”true”> <xsd:selector xpath=”.//Categories” />
<xsd:field xpath=”CategoryID” /> </xsd:unique>
<xsd:unique name=”Products_Constraint1” msdata:ConstraintName=”Constraint1” msdata:PrimaryKey=”true”>
<xsd:selector xpath=”.//Products” /> <xsd:field xpath=”ProductID” />
</xsd:unique>
<xsd:keyref name=”CategoriesProducts” refer=”Constraint1”> <xsd:selector xpath=”.//Products” />
<xsd:field xpath=”CategoryID” /> </xsd:keyref>
I can’t get into the details of XML here, but it’s easy to see the definitions of the primary and foreign keys. keyref is XML’s term for a relation. The CategoriesProducts relation is between Constraint1 (the Categories table’s primary key) and the CategoryID field of the Products table.
We now have a DataSet with two related tables, just as they appear in the database. The DataSet will be populated with a copy of the two tables the moment it’s created. You can work with the copies of the tables and update the underlying tables in the database whenever you see fit (if you edit the DataSet). The catch here is that the DataSet must be populated at once. Should you be working with a table of half a million book titles, you’d download an enormous amount of information to the client. If the user wants to view the first few titles in a couple of categories only, you’re wasting system resources. In your applications, you should limit the number of rows downloaded to the client, and you’ll see how to do this later in this chapter. Never download more data to the client than you’re going to use. Dumping thousands of rows on a DataGrid control isn’t going to be of much help to the user either.
To load the data to the grid, enter the statements shown in Listing 21.1 in the form’s Load event.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
CREATING A DATASET 941
Listing 21.1: Populating a DataSet with Two Tables
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
DACategories.Fill(CategoriesProducts1, “Categories”)
DAProducts.Fill(CategoriesProducts1, “Products”)
End Sub
Run the application now. When the form comes up, it will be empty, displaying a plus symbol where the first row should appear. Click it and you will see the names of the two tables: Categories and Products. Select the parent table, Categories, and the grid will be populated with the rows of the Categories table. In front of each row, you’ll see the plus symbol, which indicates that all the rows in the Categories table are parent rows and you can expand them to see their child rows from the Products table.
If you click one of the plus signs, you’ll see the names of all relations that use the Categories table as the parent table. In our example, there’s only one such relation, the CategoriesProducts relation. Click this link and the grid will be populated with the selected category’s child rows, as shown in Figure 21.10. At the top of the grid, you see the name of the parent table and the selected row. This is the parent row of all the rows currently on the grid (it’s the category to which all the products belong). At the top-left corner of the grid are two icons: The back arrow icons takes you back to the parent row. The other icon is a toggle that hides/displays the details of the parent row.
Figure 21.10
Viewing child rows on the DataGrid control
The headers of each column are clickable. When a header is clicked, the rows on the grid are sorted alphabetically according to the selected column. If you sort a column, a little arrow appears next to its header, indicating the order in which the rows are sorted (ascending or descending). To disable the sorting of the columns, set the DataGrid control’s AllowSorting property to False (its default value is True).
As you can see, the DataGrid control can handle related tables without any code. It picks up all the information about relations from the schema (the XSD file created for you by the wizard) and uses it to build an elaborate interface. The DataGrid is fine for building an interface to browse one or more tables, but when it comes to updating the underling tables, you must provide additional code. You’re going to learn how to write robust code for updating the underlying tables in the following chapter. In the following section, we’ll look at another simple technique for displaying data on a form, using the familiar Windows controls.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |