- •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
PERFORMING UPDATE OPERATIONS 983
Performing Update Operations
Now it’s time to look at a few really advanced database operations. Getting data out of a database, storing them to a DataSet, and even processing them on the client computer is fairly straightforward. Updating the underlying tables is also straightforward, as long as all rows and all fields have been validated. In this section, we’ll take a look at what can go wrong in moving the data from the DataSet back into the database.
As you have seen, there are two major approaches when working with ADO.NET: use the DataSet’s update method, or use the Command object to execute SQL statements and stored procedures directly against the database. Neither approach is necessarily better than the other; sometimes you’ll find DataSets more convenient to work with, sometimes not. DataSets were designed for disconnected scenarios. You can populate a DataSet with several tables, establish relations between them, and send it to Germany where someone might use it for their own purposes. Or you can take it with you to a Greek island, edit it, and bring it back two weeks later. The question is what will happen when you attempt to update the database with your DataSet’s data. If the tables are edited frequently by other users as well, then all kinds of conflicts will arise when you call the Update method. If the tables aren’t modified frequently, then there’s a good chance that most of the rows in your DataSet will successfully update the underlying tables.
You can safely use DataSets to send data to other users. You can also safely receive DataSets from other users, probably from different databases. You’ll be able to write applications to look at them, even edit them. You can also use them to update your database. The data in a DataSet sent by someone else most likely contains new data, and you can insert the appropriate rows in your database. If the same data exist in your database, you’ll probably use the new DataSet to update your database. A publisher might send you a complete list of books for your online store. It’s safe to assume that the publisher’s data contain fewer mistakes than yours and you can use the new data to update your database.
The most complicated scenario is when you want to write applications to maintain a database. Invoicing and similar applications are easy to write, because an invoice can’t be edited. Invoices are entered once, they can be viewed many times, used in calculations, but no one can change an invoice. We usually cancel an invoice in its entirety. If you want to write a front-end application for maintaining a database where things change every day, the disconnected approach of ADO.NET may not be your best bet. Here’s why. The DataSet can hold a small (or not so small) segment of your database. To make the best of the disconnected nature of the DataSet, you must keep it on the client for as long as possible. The longer you keep the disconnected DataSet on the client computer, the more you increase the chances of other users to modify the same data in the database. The problem of two or more users attempting to update the same data is as old as computers (almost) and is known as concurrency. There are two ways to deal with concurrency, optimistic concurrency and pessimistic concurrency. ADO.NET is based on optimistic concurrency.
Optimistic concurrency means that no other users will attempt to access the same data while you’re editing them. As you recall, the stored procedures generated by the DataAdapter wizard for updating the database won’t update a row if even one of its fields has changed since we last read it. If even a single field has changed value in the underlying table, then the row we have in the DataSet has been edited by someone else. Can we overwrite someone else’s edits?
Let’s think about it for a moment. If you’re editing the Customers table, you’re most likely changing addresses, phone numbers, and so on. If someone else happens to edit the same row at the same
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
984 Chapter 22 PROGRAMMING THE ADO.NET OBJECTS
time, they’re probably doing the same. Would it make any difference if you committed your edits before, or after, another user? There are many situations where the “last-write wins.” The user that updates the database last overwrites changes made by others. When you create the SQL statements or stored procedures for updating the database, you’re given the option to use optimistic concurrency or not. If you decide that the “last-write wins” scenario works for your application, don’t use optimistic concurrency.
In an airline reservation system, the “last-write wins” scenario is out of the question, obviously. We don’t want agents to assign the same seat over and over again. The same is true for an application that updates bank accounts and so on. That’s why the wizard that generates the SQL statements for updating the database uses optimistic concurrency and doesn’t update a row if it has been changed since it was read into the DataSet.
A DataRow’s Versions
How does the DataSet know that a row has been edited since it was last read? This is an interesting aspect of ADO.NET. Each row in a DataTable has several versions. The values read from the database are the Original values (this is how the DataSet knows whether a row has been modified since it read it from its table). The value of a field in the DataSet is the Current value. While the user is editing a field, the new value is the Proposed value. The Proposed value will become the Current value when the changes are written to the DataSet. Finally, the Default value is the field’s default value.
To specify which version of a field’s value you want to read, specify the second parameter of the DataRow.Item property. The following statement retrieves the Original value of the first column of the first row in the Products table of the DSProducts1 DataSet:
DSProducts1.Products.Rows(0).Item(“ProductName”, DataRowVersion.Original)
To experiment with the various versions of a row, open the EditProducts project and add a Show Versions button on its form. Then enter the statements of Listing 22.10 in the button’s Click event handler.
Listing 22.10: Reading the Versions of a DataRow
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click Dim i As Integer
Dim row As DataRow For i = 0 To 2
row = objDSProducts.Products.Rows(i)
If row.HasVersion(DataRowVersion.Current) Then Console.WriteLine(“CURRENT “ & row.Item(“ProductName”, _
DataRowVersion.Current))
End If
If row.HasVersion(DataRowVersion.Default) Then Console.WriteLine(“DEFAULT “ & row.Item(“ProductName”, _
DataRowVersion.Default))
End If
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
PERFORMING UPDATE OPERATIONS 985
If row.HasVersion(DataRowVersion.Original) Then
Console.WriteLine(“ORIGINAL “ & row.Item(“ProductName”, _
DataRowVersion.Original))
End If
If row.HasVersion(DataRowVersion.Proposed) Then
Console.WriteLine(“PROPOSED “ & row.Item(“ProductName”, _
DataRowVersion.Proposed))
End If
Next
End Sub
The event handler of Listing 22.10 displays all the versions of the first two rows in the objDSProducts DataSet (a DataSet that holds the Products table). It displays only the ProductName field’s values in all versions, so this is the field you must edit if you want to experiment with the versions of the DataRow object.
As you can see, a row may not have all possible versions, so we use the HasVersion method to find out whether a specific version exists before we attempt to retrieve it. This code prints all available versions of the first three rows of the Products table. Run the application and edit one or more of the first three rows. Then click the Show Versions button to see the versions of the first three rows at the time.
The second product’s name is “Chang.” Change it to “Chang1” and, without moving the focus to another field, click the Show Versions button. This is what you will see in the Output window:
CURRENT Chang
DEFAULT Chang1
ORIGINAL Chang
PROPOSED Chang1
The Current version is the value of the ProductName field in the DataSet, and it’s “Chang.” The Proposed value is “Chang1.” Return to the form and click another product, then back to Chang. The action of switching to another row caused the changes to be written to the DataSet. If you click the Show Versions button again, you will see the following:
CURRENT Chang1
DEFAULT Chang1
ORIGINAL Chang
For one, there’s no Proposed value. Once the changes are saved, there’s no longer a Proposed value. The current value became “Chang1,” but the Original value did not change. You must commit the changes to the database and reload the DataSet to change a row’s Original value.
As you can guess, the various versions of the values are used in validating the data and in determining whether a row has changed since it was last read from the database. The SQL statements generated by the wizard that generates the DataAdapter object for each table use the various versions of the rows to implement optimistic concurrency. The following UPDATE statement commits the
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
986 Chapter 22 PROGRAMMING THE ADO.NET OBJECTS
changes made to a row to the underlying table, but only if the row’s fields have the same values read into the DataSet:
UPDATE dbo.Products SET ProductName = ?, SupplierID = ?, CategoryID = ?, QuantityPerUnit = ?, UnitPrice = ?, UnitsInStock = ?, UnitsOnOrder = ?, ReorderLevel = ?, Discontinued = ?
WHERE (ProductID = ?) AND
(CategoryID = ? OR ? IS NULL AND CategoryID IS NULL) AND (Discontinued = ?) AND
(ProductName = ?) AND
(QuantityPerUnit = ? OR ? IS NULL AND QuantityPerUnit IS NULL) AND (ReorderLevel = ? OR ? IS NULL AND ReorderLevel IS NULL) AND (SupplierID = ? OR ? IS NULL AND SupplierID IS NULL) AND (UnitPrice = ? OR ? IS NULL AND UnitPrice IS NULL) AND (UnitsInStock = ? OR ? IS NULL AND UnitsInStock IS NULL) AND (UnitsOnOrder = ? OR ? IS NULL AND UnitsOnOrder IS NULL)
The question marks correspond to parameters, and they’re substituted with actual values prior to executing this statement. If you turn off optimistic concurrency, the wizard will generate the following simple statement:
UPDATE dbo.Products SET ProductName = ?, SupplierID = ?, CategoryID = ?, QuantityPerUnit = ?, UnitPrice = ?, UnitsInStock = ?, UnitsOnOrder = ?, ReorderLevel = ?, Discontinued = ?
WHERE (ProductID = ?)
A DataRow’s States
In addition to versions, rows have states, too; a row can be in one of the following states:
Added The row has been added to the DataTable, but it hasn’t been accepted yet (rows are accepted after they’re written to the database as well).
Deleted The row has been deleted. However, it remains in the DataSet marked as Deleted, so that the Update method can delete the matching row of the underlying table.
Detached The row has been created but it has not been added to a DataTable yet. A row is in this state while you set its fields and before you actually add it to a table.
Modified The row has been modified, but it hasn’t been accepted yet.
Unchanged The row hasn’t been changed yet.
The state of a row is used in updating the underlying tables. When you call the DataAdapter’s Update method, all rows are moved to the database, where they’re committed with the appropriate SQL statement or stored procedure. To conserve bandwidth, you can send only the modified, added, and deleted rows to the database. This is the technique used by the DataForm wizard, and it’s worth taking a look at the code it produces. All the action of updating the database takes place in the UpdateDataSource() function, which returns the number of rows that were updated successfully. Listing 22.11 shows the code of the UpdateDataSource() function.
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |
PERFORMING UPDATE OPERATIONS 987
Listing 22.11: The UpdateDataSource() Function of the DataForm Wizard
Public Function UpdateDataSource(ByVal dataSet As EditProducts.DSProducts) _ As System.Int32
Me.OleDbConnection1.Open()
Dim UpdatedRows As System.Data.DataSet
Dim InsertedRows As System.Data.DataSet
Dim DeletedRows As System.Data.DataSet Dim AffectedRows As Integer = 0
UpdatedRows = DataSet.GetChanges(System.Data.DataRowState.Modified) InsertedRows = DataSet.GetChanges(System.Data.DataRowState.Added) DeletedRows = DataSet.GetChanges(System.Data.DataRowState.Deleted) Try
If (Not (UpdatedRows) Is Nothing) Then
AffectedRows = OleDbDataAdapter1.Update(UpdatedRows)
AffectedRows = (AffectedRows + OleDbDataAdapter2.Update(UpdatedRows)) AffectedRows = (AffectedRows + OleDbDataAdapter3.Update(UpdatedRows))
End If
If (Not (InsertedRows) Is Nothing) Then
AffectedRows = (AffectedRows + OleDbDataAdapter1.Update(InsertedRows)) AffectedRows = (AffectedRows + OleDbDataAdapter2.Update(InsertedRows)) AffectedRows = (AffectedRows + OleDbDataAdapter3.Update(InsertedRows))
End If
If (Not (DeletedRows) Is Nothing) Then
AffectedRows = (AffectedRows + OleDbDataAdapter1.Update(DeletedRows)) AffectedRows = (AffectedRows + OleDbDataAdapter2.Update(DeletedRows)) AffectedRows = (AffectedRows + OleDbDataAdapter3.Update(DeletedRows))
End If
Catch updateException As System.Exception Throw updateException
Finally Me.OleDbConnection1.Close()
End Try
End Function
This function generates three new DataSets and populates them with the added, deleted, and modified rows of the original DataSets. In a large DataSet, the three partial DataSets will be considerably smaller than the original DataSet. Then, it calls the Update method of the corresponding DataAdapter object, passing the appropriate DataSet.
The order of the DataSets passed to the Update method is important. First it passes the rows that were edited, then the new rows, and finally the deleted rows.
If you examine the code generated by the wizard, you’ll see that it doesn’t reload the DataSet. Instead, it merges the rows that successfully updated the underlying tables with the existing rows in the DataSet (it basically consolidates the changes) and then calls the DataSet object’s AcceptChanges method. AcceptChanges sets the Original value of all fields to the Current value. In effect, the new DataSet is the same as the one you would get by reloading it from the database. If your DataSet isn’t
Copyright ©2002 SYBEX, Inc., Alameda, CA |
www.sybex.com |