Visual CSharp .NET Developer's Handbook (2002) [eng]
.pdfFigure 11.6: The Tracing tab contains options for logging each ODBC action on a particular DSN.
The trace won't start automatically. You'll need to click Start Tracing Now. The push button caption will change to Stop Tracing Now as soon as tracing starts. Click on the button again to turn tracing off.
Note You might see variations in the content of the Tracing tab based on the version of Windows that you use and the Visual Studio features you install. The version shown is for Windows XP with Visual Studio Analyzer installed. In some cases, you'll see three radio buttons that determine when you'll trace the ODBC calls. The default setting is Don't Trace. You'd select All the Time if you were going to work on debugging a single application. The One-Time Only traces the ODBC calls during the next connection— tracing gets turned off as soon as the connection is broken. This is a good selection to choose when a user calls in with a specific problem. You can monitor the connection during one session and then use that information to help you create a plan for getting rid of the bug.
The only other setting that you'll need to worry about is the Log File Path. ODBC normally places the transaction information in the SQL.LOG file in your root directory. However, you may want to place the logging information on a network drive or use a location hidden from the user. The default location normally works fine during the debugging process.
Note Unless you want to create your own logging DLL, don't change the setting in the Custom Trace DLL field. The DLL listed here, ODBCTRAC.DLL, is responsible for maintaining the transaction log.
An Overview of OLE-DB and ADO
One of the more confusing things about working with ADO is understanding that it's not the lowest rung on the ladder. OLE-DB is the basis for anything you do with ADO; it provides the basis for communication with the database. ADO is simply a nice wrapper around the services that OLE-DB provides. In fact, you can even bypass ADO and go right to OLE-DB if you want to. However, using ADO will help you to develop applications much faster. The following sections will help you understand both OLE-DB and ADO.
Understanding OLE-DB
So, what is OLE-DB? As the name implies, it uses OLE (or more specifically, the component object model—COM) to provide a set of interfaces for data access. Just like any other COM object, you can query, create, and destroy an OLE-DB object. The source of an OLE-DB object is a provider. The .NET Framework includes only a few of OLE-DB providers found in the unmanaged version of the product. More will likely arrive as vendors upgrade their database products. The nice thing about OLE-DB is that the same provider works with any Visual Studio product: Visual C++, Visual Basic, and C#.
OLE-DB also relies on events, just as any COM object would. These events tell you when an update of a table is required to show new entries made by other users or when the table you've requested is ready for viewing. You'll also see events used to signal various database errors and other activities that require polling right now.
Microsoft defines four major categories of OLE-DB user. It's important to understand how you fit into the grand scheme of things. The following list breaks the various groups down and describes how they contribute toward the use of OLE-DB as a whole.
Data Provider A developer who creates an OLE-DB provider using the OLE-DB SDK (Software Development Kit). The provider user interfaces to interact with the database and events to signal special occurrences.
Data Consumer An application, system driver, or user that requires access to the information contained in a database.
Data Service Provider A developer who creates stand-alone utilities (services) that enhance the user's or administrator's ability to use or manage the contents of a database. For example, a developer could create a query engine that allows the user to make natural language requests for information in the database. A service works with the OLE-DB provider and becomes an integral part of it.
Business Component Developer A developer who creates application modules or components that reduce the amount of coding used to create a database application. A component could be something as generic as a grid control that allows you to display a subset of the records in the database at a glance or something specific to the type of database being accessed.
Microsoft designed OLE-DB as an upgrade to ODBC. The fact is that many people still use ODBC because they perceive it as easier to use than OLE-DB or ADO. Note that this view is so pervasive that Microsoft finally created ODBC.NET for those developers who refuse to make the change. In addition, more database vendors provide ODBC access (although this is changing now). So, how does OLE-DB differ from ODBC? Table 11.1 shows the major differences between the two products. We'll discuss how these differences affect your usage decisions later in this chapter.
|
Table 11.1: OLE-DB to ODBC Technology Comparison |
||||||
|
|
|
|
|
|
|
|
Element |
|
|
OLE-DB |
|
ODBC |
|
Comments |
|
|
|
|
|
|
|
|
Access type |
|
|
Component |
|
Direct |
|
OLE-DB provides interfaces that |
|
|
|
|
|
|
|
|
Table 11.1: OLE-DB to ODBC Technology Comparison
Element |
|
OLE-DB |
|
ODBC |
|
Comments |
|
|
|
|
|
|
|
|
|
|
|
|
|
interact with the data. User access |
|
|
|
|
|
|
to the data is through components |
|
|
|
|
|
|
designed to interact with OLE-DB. |
|
|
|
|
|
|
|
Data access |
|
Any tabular data |
|
SQL |
|
Microsoft designed ODBC to use |
specialization |
|
|
|
|
|
SQL as the basis for data |
|
|
|
|
|
|
transactions. In some cases, that |
|
|
|
|
|
|
means the programmer has to make |
|
|
|
|
|
|
concessions to force the data to fit |
|
|
|
|
|
|
into the SQL standard. |
|
|
|
|
|
|
|
Driver access method |
|
Component |
|
Native |
|
As mentioned earlier, all access to |
|
|
|
|
|
|
an OLE-DB provider is through |
|
|
|
|
|
|
COM interfaces using components |
|
|
|
|
|
|
of various types. ODBC normally |
|
|
|
|
|
|
requires direct programming of |
|
|
|
|
|
|
some type and relies heavily on the |
|
|
|
|
|
|
level of SQL compatibility |
|
|
|
|
|
|
enforced by the database vendor. |
|
|
|
|
|
|
|
Programming model |
|
COM |
|
C/C++ |
|
OLE-DB relies on COM to provide |
|
|
|
|
|
|
the programmer with access to the |
|
|
|
|
|
|
provider. This means that OLE-DB |
|
|
|
|
|
|
is language independent, while |
|
|
|
|
|
|
ODBC is language specific. |
|
|
|
|
|
|
|
Technology standard |
|
COM |
|
SQL |
|
OLE-DB adheres to Microsoft's |
|
|
|
|
|
|
COM standard, which means that |
|
|
|
|
|
|
it's much more vendorand |
|
|
|
|
|
|
platform-specific than the SQL |
|
|
|
|
|
|
technology standard used by |
|
|
|
|
|
|
ODBC. |
Don't get the idea that OLE-DB and ODBC are two completely separate technologies meant to replace each other. Microsoft provides an ODBC OLE-DB provider that enables you to access all of the functionality that ODBC provides through OLE-DB or ADO. In other words, the two technologies complement each other and don't act as complete replacements for each other. (Unfortunately, this cross compatibility doesn't translate well to the .NET Framework where OLE-DB and ODBC are separate entities.)
Can you replace ODBC with ADO or OLE-DB? Yes, but you won't get the very best performance from your applications if you do. The whole idea of OLE-DB is to broaden the range of database types that you can access with your C# applications. Obviously, if you do need to access both ODBC and tabular data with a single application, OLE-DB provides one of the better solutions for doing so.
Understanding ADO
Now that you have a little better handle on OLE-DB, where does ADO fit in? As previously mentioned, ADO provides an easy method for accessing the functionality of an OLE-DB
provider. In other words, ADO helps you to create applications quickly and enables C# to take care of some of the details that you'd normally have to consider when using OLE-DB directly. ADO is a wrapper for OLE-DB and reduces the number of steps required to perform common tasks.
ADO represents a new way to provide database access through the combination of databound ActiveX controls and five specialty classes. You can divide the classes into two functional areas: data provider and dataset.
The data provider contains classes that provide connection, command, data reader, and data adapter support. The connection provides the conduit for database communications. The command enables the client to request information from the database server. It also enables the client to perform updates and other tasks. The data reader is a one-way, read-only, disconnected method of viewing data. The data adapter provides the real-time connection support normally associated with live data connections.
The dataset is the representation of information within the database. It contains two collections: DataTableCollection and DataRelationCollection. The DataTableCollection contains the columns and rows of the table, along with any constraints imposed on that information. The DataRelationCollection contains the relational information used to create the dataset.
ADO provides several advantages over previous database access methods. The following list will describe them for you.
Independently Created Objects You no longer have to thread your way through a hierarchy of objects. This feature permits you to create only the objects you need, reducing memory requirements and enhancing application speed.
Batch Updating Instead of sending one change to the server, you can collect them in local memory and send all of them to the server at once. Using this feature improves application performance (because the data provider can perform the update in the background) and reduces network load.
Stored Procedures These procedures reside on the server as part of the database manager. You'll use them to perform specific tasks on the dataset. ADO uses stored procedures with in/out parameters and a return value.
Multiple Cursor Types Essentially, cursors point to the data you're currently working with. You can use both client-side and server-side cursors.
Returned Row Limits You only get the amount of data you actually need to meet a user request.
Multiple Recordset Objects Helps you to work with multiple recordsets returned by stored procedures or batch processing.
Free-Threaded Objects This feature enhances web server performance.
There are two databinding models used for ActiveX controls. The first, simple databinding provides the means for an ActiveX control like a text box to display a single field of a single record. The second, complex databinding enables an ActiveX control like a grid to display multiple fields and records at the same time. Complex databinding also requires the ActiveX control to manage which records and fields the control will display. C# comes with several ActiveX controls that support ADO, including the following:
•DataGrid
•DataCombo
•DataList
•Hierarchical Flex Grid
•Date and Time Picker
Like OLE-DB, Microsoft based ADO on COM. ADO provides a dual interface: a program ID of ADODB for local operations and a program ID of ADOR for remote operations. The ADO library itself is free-threaded, even though the registry shows it as using the apartmentthreaded model. The thread safety of ADO depends on the OLE-DB provider that you use. In other words, if you're using Microsoft's ODBC OLE-DB provider, you won't have any problems. If you're using a third-party OLE-DB provider, you'll want to check the vendor documentation before assuming that ADO is thread safe (a requirement for using ADO over an Internet or intranet connection).
You'll use seven different objects to work with ADO. Table 11.2 lists these objects and describes how you'll use them. Most of these object types are replicated in the other technologies that Microsoft has introduced, although the level of ADO object functionality is much greater than that offered by previous technologies.
|
|
Table 11.2: ADO Object Overview |
|
|
|
Object |
|
Description |
|
|
|
Command |
|
A command object performs a task using a connection or recordset |
|
|
object. Even though you can execute commands as part of the |
|
|
connection or recordset objects, the command object is much more |
|
|
flexible and allows you to define output parameters. |
|
|
|
Connection |
|
Defines the connection with the OLE-DB provider. You can use this |
|
|
object to perform tasks like beginning, committing, and rolling back |
|
|
transactions. There are also methods for opening or closing the |
|
|
connection and for executing commands. |
|
|
|
Error |
|
ADO creates an error object as part of the connection object. It provides |
|
|
additional information about errors raised by the OLE-DB provider. A |
|
|
single error object can contain information about more than one error. |
|
|
Each object is associated with a specific event, like committing a |
|
|
transaction. |
|
|
|
Field |
|
A field object contains a single column of data contained in a recordset |
|
|
object. In other words, a field could be looked at as a single column in a |
|
|
table and contain one type of data for all of the records associated with |
|
|
a recordset. |
|
|
|
Parameter |
|
Defines a single parameter for a command object. A parameter |
|
|
modifies the result of a stored procedure or query. Parameter objects |
|
|
|
|
|
Table 11.2: ADO Object Overview |
|
|
|
Object |
|
Description |
|
|
|
|
|
can provide input, output, or both. |
|
|
|
Property |
|
Some OLE-DB providers will need to extend the standard ADO object. |
|
|
Property objects represent one way to perform this task. A property |
|
|
object contains attribute, name, type, and value information. |
|
|
|
Recordset |
|
Contains the result of a query and a cursor for choosing individual |
|
|
elements within the returned table. C# gives you the option of creating |
|
|
both a connection and a recordset using a single recordset object or |
|
|
using an existing connection object to support multiple recordset |
|
|
objects. |
|
|
|
Writing an OLE-DB Application
The OLE-DB example will rely on a contact database that contains name and address information for the customers in a company. You'll find the database in the \Chapter 11\DSN folder as MyData.MDB. The example code appears in the \Chapter 11\OLEDB folder on the CD. The following sections show you how to create and code an application that provides both grid and detail views.
Note The examples in this chapter rely on a Microsoft Access database for input. I chose this product because many people ask for Access-specific examples on the various Visual Studio newsgroups and because I've received e-mails asking for this type of example. The examples in Chapters 12 and 13 will rely on SQL Server 2000. The combination of the two database sources should provide you with a better view of how well .NET works for a variety of database application needs. Please feel free to contact me at JMueller@mwt.net with comments.
Creating the Grid View
This example requires more setup than examples in previous chapters. You have to create a connection to the database, provide commands for manipulating the database, use a data adapter to create a place to store the data, and finally, generate a dataset to store the results of the data query. As you'll learn, the Server Explorer in the Visual Studio IDE helps make application creation faster and easier. The following steps show how to use Server Explorer to set up your application.
1.Open the Server Explorer toolbar shown in Figure 11.7 by clicking on the top (rather than the bottom) of the toolbar area on the left side of the Visual Studio IDE. As you can see, this display provides immediate access to local machine resources and data connections. You can also create connections to remote machines.
Figure 11.7: The Server Explorer enables you to create new connections or use existing resources.
2.Right-click Data Connections and choose Add Connection from the context menu. You'll see the Data Link Properties dialog box shown in Figure 11.8. Note that you might have to manually select the Provider tab. Selecting the right provider is an essential part of the application configuration process.
Figure 11.8: Visual Studio .NET includes the list of providers shown in this figure.
3.Select the Microsoft Jet 4.0 OLE-DB Provider option, and then click Next. You'll see a Connection tab that includes the name of the database and logon credentials.
4.Select the database you want to use (the example uses the MyData.MDB file), and then click Test Connection. It's essential to perform the connection test to ensure errors you see in the application aren't due to a lack of access. You should see a Test Connection Succeeded message. If not, try to create the connection again.
Note The Advanced tab contains options that may increase your chances of accessing a database, especially a remote resource. This tab contains options for selecting an impersonation level, protection level, connection timeout, and type of access. The connection timeout value is especially important for Internet resources, because the default timeout assumes a LAN connection. Likewise, the wizard assumes you want to create a Share Deny None connection when you really need a Share Exclusive connection for the application.
5.Click OK to create the connection. The Visual Studio IDE may pause for a few seconds, at this point, to establish the connection. When the IDE establishes the connection, you'll see a new connection in the Server Explorer. An Access database typically provides tables, views, and stored procedures, but you might see other entries within the connection.
6.Locate the Address table in the Tables folder. Drag the Address table to the form. Visual Studio will automatically generate a connection and data adapter for you. Make absolutely certain the data adapter contains entries for the DeleteCommand,
InsertCommand, SelectCommand, and UpdateCommand properties or the example won't work properly. You'll want to rename the connection and data adapter—the example uses AddressConnection and AddressDA.
7.Right-click AddressDA and choose Generate Dataset from the context menu. You'll see a Generate Dataset dialog box.
8.Type AddressDataset in the New field, then click OK. Visual Studio .NET will automatically generate the dataset for you.
The example application uses a menu driven system and includes a toolbar for quick access to commands. It relies on a DataGrid control to display the data. One of the DataGrid configuration tasks is to ensure the DataSource property points to foDataSet1 and the DataMember property points to the FoodOrders query. Check the project on the CD for the remaining configuration items.
Tip The toolbar uses standard icons for the Print and Print Preview menu options (among others). You can find a list of standard icons in the \Program Files\Microsoft Visual Studio .NET\Common7\Graphics\bitmaps\OffCtlBr\Small\Color folder of your Visual Studio installation. Add these icons to an ImageList control, which you can then access within the ToolBar control as a source of images. The example also uses arrows from the \Program Files\Microsoft Visual Studio .NET\Common7\Graphics\icons\arrows folder and modified versions of the arrows found in the \Chapter 11\Graphics folder for record movement icons.
You'll want to pay special attention to the DataGrid configuration because some elements aren't obvious. Look at the TableStyles property and you'll notice that it's a collection not a single value. Click the ellipses button next to the property and you'll see a DataGridTableStyle Collection Editor dialog, similar to the one shown in Figure 11.9. Notice
that this dialog already has an entry in it. You need one entry for each table or query that you want to add to the data grid.
Figure 11.9: The DataGridTable-Style Collection Editor enables you to configure a table for display.
Tip Any modifications you make to the DataGridTableStyle Collection Editor or DataGridColumnStyle Collection Editor dialog box entries will appear in bold type. The use of bold text makes it easy to find any changes you've made.
As you can see, the properties in this dialog box help you set table attributes, such as the default column width and the cell selection color. To make these settings work, you must set the MappingName property to the table or query that you want to configure for display. You also need to configure the GridColumnStyles property, which is another collection. Click the ellipses button and you'll see a DataGridColumnStyle Collection Editor dialog box, like the one shown in Figure 11.10. This figure also shows the columns required for the example application.
Figure 11.10: Configuring the table also implies that you'll configure the associated columns.
Figure 11.10 shows a typical DataGridColumnStyle object entry. You must provide a MappingName property value. In this case, the MappingName refers to a single column
within the table or query. Generally, you'll find that you need to provide values for the HeaderText, NullText, and Width properties. The Format property is a special entry that you'll want to use for special value types such as dates and currency. Because the DataGridColumnStyle object entry in Figure 11.10 is for a date value, the Format property contains an uppercase D for a long date format. You'd use a lowercase d for a short date format. The .NET Framework provides a wealth of format string values that you can learn about in the Formatting Types (mshelp://MS.VSCC/MS.MSDNVS/cpguide/html/cpconformattingtypes.htm) help topic.
Coding the Grid View
Coding database applications can become quite complex depending on the number of tables and application features you want to provide. However, there are certain coding tasks that every Grid view application will have. The first requirement is some means of filling the grid with data. You'll find the following line of code in the GridView class constructor.
AddressDA.Fill(addressDataset1);
This line of code tells the data adapter to fill the data set with data. The connection to the data grid is automatic because of the DataSource and DataMember property entries. The various MappingName entries ensure the data appears in the correct order and in the correct format.
While the connection between the dataset and the data grid is automatic, the connection between the dataset and the data adapter isn't. Changing a value in the data grid doesn't guarantee that it will appear in the database as well. In fact, there are a number of conditions that will prevent the data from ever appearing in the database. For example, you can configure the dataset as read-only, which would prevent changes at a basic level. However, for any change to take place, you must detect the change using the CurrentCellChanged() event and then update the data adapter with the new data. Listing 11.1 shows the code you'll need to perform data grid updates.
Listing 11.1: Updating the Data Adapter
private void AddrDataGrid_CurrentCellChanged(object sender, System.EventArgs e)
{
DialogResult DR; // Used to store the result of a dialog.
try
{
// Update the record to reflect user changes. AddressDA.Update(addressDataset1);
}
catch (DBConcurrencyException DBCE)
{
//If an error occurs, see if the user wants
//to exit the application.
DR = MessageBox.Show("Concurrency Error\r\n" +
DBCE.Message + "\r\n" +
DBCE.Source + "\r\n" +
DBCE.Row + "\r\n" +
"Exit Application?",
"Database Update Error",
MessageBoxButtons.YesNo,