Microsoft's .NET Framework is gaining acceptance among solution
developers who traditionally rely on Microsoft development tools. Some of these
developers want to take advantage of the robust transaction support, tight
security, reliable messaging, and workload and system management that are unique
strengths of iSeries servers. It seems that the combination of .NET Framework
and a powerful database server, such as DB2 UDB for iSeries, can result in a
scalable software solution that's very appealing to the development
Typically, .NET applications use ADO.NET classes to access and manipulate data stored in databases. ADO.NET is an evolutionary improvement to the Microsoft ActiveX Data Objects (ADO) programming interface. This article discusses how to efficiently implement the ADO.NET programming model to access the iSeries database. I'll also illustrate using DB2 add-ins for Visual Studio .NET to significantly speed up the development process.
The .NET applications rely on services of .NET providers for communication with back-end database servers. A .NET data provider is used for connecting to a database, executing commands, and retrieving results. Currently, four providers can be used to access DB2 UDB from .NET applications:
- V5R3 iSeries Access .NET provider implemented by IBM Rochester
- The DB2 for Linux, UNIX, and Windows (LUW) V8.2 (formerly Stinger) .NET provider implemented by IBM software group
- ODBC .NET Data Provider, the Microsoft-supplied ODBC bridge provider using the iSeries Access for Windows ODBC driver for underlying database connectivity
- OLE DB .NET Data Provider, the Microsoft-supplied OLE DB bridge provider using the iSeries Access OLE DB driver for underlying database connectivity
The OLE DB .NET provider and the ODBC .NET provider are called "unmanaged" providers, meaning that the code has been compiled directly into a binary executable, so it runs outside of the .NET framework. The unmanaged providers usually deliver suboptimal performance because they involve jumping in and out of the .NET Framework environment for every interface call. On the other hand, the iSeries Access .NET provider and the DB2 .NET provider are called "managed" providers. Managed providers are compiled into .NET assemblies that can be executed in the context of the .NET Framework. In this case, the application/provider interaction does not require API calls that cross the .NET Framework boundary.
The iSeries Access .NET Provider
The iSeries Access .NET provider is sometimes called
"native iSeries .NET provider." It uses a highly optimized, proprietary protocol
to communicate with the iSeries database server job. The database server jobs
are called QZDASOINIT, and they typically run in the QUSRWRK subsystem. A
database server job runs the SQL requests on behalf of the .NET provider. More
precisely, when a .NET application submits an SQL statement, the provider passes
the statement to a server job that, in turn, calls the DB2 runtime to execute
the statement. The results are then reformatted and returned to the caller.
The provider supports a full set of .NET data types and provides rich SQL functionality to enable applications to easily access and process data stored on your iSeries. These features are currently implemented in the iSeries Access .NET provider:
- SQL (INSERT, UPDATE, DELETE, SELECT)
- Commitment control
- Connection pooling
- SQL naming
- IASPs (multiple databases)
- Stored procedure support
Two additional features have been recently shipped in the iSeries Access SI15176 Service Pack:
- System naming ( / )
- Large Objects (LOBs)
The following functionality is not yet available through the provider:
- SQL package support (extended dynamic SQL)
- Data links
- User-defined types
- Record-level access
- CMD/PGM call
- Data queues
The iSeries Access .NET provider ships with iSeries Access V5R3. It supports the iSeries servers running i5/OS V5R3 and OS/400 V5R2. Note that some functionality, such as 63-digit decimal precision, is available only on V5R3 servers. For the best code stability, you should always load the latest database group PTF on the iSeries system (SF99503 for V5R3 or SF99502 for V5R2) as well as the most recent V5R3 iSeries Access Service Pack. As with any other managed provider, the .NET Framework needs to be already installed on your PC.
This section covers implementation details for a
sample Visual Basic (VB) .NET application called DB2i5Access (download here).
The program uses the iSeries Access .NET provider to obtain a database
connection and to manipulate database rows in a DB2 table called STAFF. The
table is part of the sample DB2 schema, and it can be created with this stored
The procedure has been shipped with i5/OS and OS/400 since
The application works in the disconnected mode. In the disconnected mode, a DataAdapter object retrieves data from the database and stores it in a DataSet object that serves as a local data cache. A .NET application can use the data cached in a DataSet without requiring multiple trips to the database. A DataAdapter can also implicitly update the central database with changes made to the DataSet. We used Microsoft Visual Studio .NET 2003 to develop the sample application. The main application dialog is shown in Figure 1.
Figure 1: Here's your sample DB2i5Access application.
After a new project is created, a reference to the
assembly that contains the iSeries Access .NET provider needs to be added to the
solution. You can accomplish this task by right-clicking the References icon in
Solutions Explorer and selecting Add Reference. In the Add Reference dialog,
find the IBM DB2 for iSeries .NET provider component and click Select and then
OK. It is also very useful to add the iSeries Access provider classes to the
Visual Studio Toolbox. To do so, select Tools > Add/Remove Toolbox items, and
on the Customize Toolbox dialog, check the iSeries provider-specific classes
(iDB2Command, iDB2CommandBuilder, iDB2Connection, and iDB2DataAdapter). Now,
drag and drop the iSeries provider classes onto the design pane. Currently, the
iSeries .NET provider does not provide wizards to allow you to visually
configure the classes required to access and modify DB2 data, so most of the
tasks need to be accomplished programmatically. As mentioned, in the
disconnected mode, there are several ADO.NET objects that cooperate to provide
the database access mechanism. The required objects and their relationships are
illustrated in Figure 2.
Figure 2: In the disconnected mode, ADO.NET objects provide the database
Let's have a closer look at the most critical source code fragments that need to be manually implemented by a database developer.
An ADO.NET connection object establishes a connection
to a specific data source. For instance, the iDB2Connection object connects to
DB2 UDB for iSeries. To successfully open an iSeries connection, security
information such as user ID and password must be passed to the database.
Typically, a ConnectionString property provides this information. The following
code shows how to use the iDB2Connection object:
Me.conMyiSeries = New iDB2Connection
Me.conMyiSeries.ConnectionString = "DataSource=PWD1;Connection
Note the syntax of the keywords on the connection string. The keywords are provider-specific and usually differ for different databases. In addition to the authentication info, the iSeries connection string may also specify other connection parameters, such as Default Collection (schema), Default Isolation Level, Pooling, etc. Consult the DB2 UDB for iSeries .NET provider Technical Reference for a complete list of supported parameters. The above code sample, for illustration purposes, has the user ID and password hard-coded in the application program. In fact, our sample application uses TextBox controls to allow the user to specify valid credentials at runtime.
- Connection--A reference to a connection object used for communication with the database
- CommandType--Set to one of the following values: Text, StoredProcedure, TableDirect
- CommandText--Contains the text of an SQL statement or the name of the stored procedure to be executed
- Parameters--Values required by parameterized SQL statement or stored procedure
In our scenario, we instantiate four iDB2Command
objects that the iDB2DataAdapter needs. They represent the four fundamental SQL
statements: SELECT, INSERT, UPDATE, and DELETE. The following code illustrates
how to create and set up iDB2Command objects:
Me.cmdSelect = New iDB2Command
Me.cmdUpdate = New iDB2Command
Me.cmdDelete = New iDB2Command
Me.cmdInsert = New iDB2Command
Me.cmdSelect.CommandText = "SELECT * FROM STAFF" 
Me.cmdSelect.Connection = Me.conMyiSeries
Me.cmdDelete.CommandText = "DELETE FROM DB2USER.STAFF WHERE ID = ?" 
Me.cmdDelete.Connection = Me.conMyiSeries
iDB2DbType.iDB2SmallInt, 5, System.Data.ParameterDirection.Input, _
False, CType(0, Byte), CType(0, Byte), "ID", _
For clarity, UPDATE and INSERT statements are not listed here. At , the SELECT command is set up to retrieve all rows from the STAFF table over the conMyiSeries connection. At , a parameterized DELETE statement is specified. The statement uses a parameter marker (?) as a placeholder for the ID value that will be provided at runtime. The parameter markers foster a more efficient processing of SQL statements because a parameterized statement can be prepared once and executed many times. The DELETE statement requires an iDB2Parameter object that is bound to the parameter marker. Note that the original value of the ID column is used to locate the staff member row in DB2. The statement unconditionally deletes the corresponding row in the database. In other words, it does not take into account whether the original row image has been modified by other users. This is probably not the best business practice. In a more realistic scenario, we would probably use the so-called "optimistic locking approach," where we would check if the local image still matched the image on the database server before deleting a row. This could be accomplished with a DELETE statement modified as shown below:
DELETE FROM DB2USER.STAFF WHERE ( (ID = ?)
AND ((CAST( ? AS VARCHAR(9)) IS NULL AND CAST(NAME AS VARCHAR(9)) IS NULL)
OR (CAST(NAME AS VARCHAR(9)) = ?))
AND ((CAST( ? AS SMALLINT) IS NULL AND CAST(DEPT AS SMALLINT) IS NULL)
OR (CAST(DEPT AS SMALLINT) = ?))
AND ((CAST( ? AS CHAR(5)) IS NULL AND CAST(JOB AS CHAR(5)) IS NULL)
OR (CAST(JOB AS CHAR(5)) = ?)) AND ((CAST( ? AS SMALLINT) IS NULL
AND CAST(YEARS AS SMALLINT) IS NULL) OR (CAST(YEARS AS SMALLINT) = ?))
AND ((CAST( ? AS DEC(7,2)) IS NULL AND CAST(SALARY AS DEC(7,2)) IS NULL)
OR (CAST(SALARY AS DEC(7,2)) = ?))
AND ((CAST( ? AS DEC(7,2)) IS NULL AND CAST(COMM AS DEC(7,2)) IS NULL)
OR (CAST(COMM AS DEC(7,2)) = ?)) )
The same approach could also be applied to the UPDATE statement.
An iDB2DataAdapter is a bridge that links an iSeries
database and a local data cache represented by a DataSet object. The
iDB2DataAdapter encapsulates a database connection and a set of SQL commands
that are used to retrieve the data from the database and update the database
with the changes made to the DataSet. Once the connection and the command's
objects have been defined, the iDB2DataAdapter configuration is pretty
straightforward. See the following code:
Me.daStaff.InsertCommand = Me.cmdInsert
Me.daStaff.SelectCommand = Me.cmdSelect
Me.daStaff.UpdateCommand = Me.cmdUpdate
Exception handling in ADO.NET complies with the
Common Language Runtime (CLR) specification and is consistent across all
supported .NET programming languages. In CLR, the control structure is the
Try-Catch-Finally statement. An error that occurs in the Try block is handled in
the Catch block. The iSeries Access .NET provider implements the iDB2Exception
object that can be used to monitor for the provider-specific exceptions. The
provider throws an iDB2Exception whenever it encounters an error generated from
the iSeries host server or from the underlying iSeries Access APIs .
The iDB2Exception contains a collection of one or more iDB2Error objects. The DB2i5Access application uses the iDB2Exception to intercept database errors that can occur at the time the rows are loaded into the DataSet or the updates are submitted to the database. For example, the btnLoad_Click method monitors for iSeries-specific errors and shows an appropriate error message box when unexpected conditions are encountered. See the following code fragment for implementation details:
Private Sub btnLoad_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnLoad.Click
Catch Xcp As iDB2Exception
Dim sqle As iDB2Error
For Each sqle In Xcp.Errors
MessageBox.Show(Xcp.ToString(), "DB2 Server Error", _
Troubleshooting a multi-tier application is usually not trivial because the developer needs to deal with software components that reside in different operating system environments. Two tools can be particularly useful for problem resolution:
- The job log messages for a database server job usually provide enough details to isolate DB2 UDB for iSeries runtime issues.
- The iSeries Access .NET provider trace utility provides granular information about the data flow between the .NET application and the iSeries server job.
The iSeries Access .NET provider communicates with a
corresponding iSeries server job. This server job runs the SQL requests on
behalf of the .NET client. The iSeries database server jobs are called
QZDASOINIT, and they run in the QUSRWRK subsystem. At any given time, many
database server jobs may be active on the system, so the first step is to
identify the job that serves the particular client connection, which is easily
done by running the following CL command:
Here, DB2USER is the user profile that is used to connect to the iSeries
server. This approach works even in the disconnected mode. Thanks to connection
pooling, a corresponding job is held even after the iDB2DataAdapter has closed
the database connection.
Sometimes, it is also useful to include debug messages in the job log. The debug messages are informational messages written to the job log about the implementation of a query. They describe query implementation methods, such as use of indexes, join order, open data path (ODP) implementation (reusable versus non-reusable), and so on. The easiest way to instruct the iSeries to include the debug messages is to set the Trace property on the connection string of the iDB2Connection object:
A sample of the job log messages with debug messages enabled is shown
**** Starting optimizer debug message for query .
Unable to retrieve query options file.
All access paths were considered for file STAFF.
Arrival sequence access was used for file STAFF.
**** Ending debug message for query .
Blocking used for query.
Cursor C000001 opened.
DESCRIBE of prepared statement S000001 completed.
35 rows fetched from cursor C000001.
Cursor C000001 was closed.
Provider Trace Utility
The iSeries Access .NET provider ships with a
tracing utility, which can be used to collect detailed client-side traces. The
.NET provider tracing is turned off by default. It can be enabled by calling the
CWBMPTRC program on Windows. Here's an example of how to switch on
The trace file by default is named IDB2TRACE.TXT and is stored in the C:Documents and SettingsAll UsersDocumentsIBMClient Access directory.
DB2 UDB V8.2 .NET Provider
The recently announced IBM DB2 UDB for Linux, UNIX, Windows (LUW) V8.2 (formerly "Stinger") provides a high level of integration with Visual Studio .NET, improved performance for .NET applications, and significant enhancements for stored procedure and user-defined function developers. This new version also introduced .NET connectivity and simplified programming facilities for building applications that work with DB2 UDB for iSeries servers. The iSeries support relies on services provided by DB2 Connect. DB2 Connect is offered as separate Windows licensed products:
- Enterprise Edition
- Personal Edition
- DB2 Connect Unlimited Edition
- Application Server Edition
The DB2 Connect Enterprise
Edition functionality is also contained in DB2 Enterprise Server Edition for
DB2 Connect implements the Distributed Relational Database Architecture (DRDA) to access data stored in DB2 UDB for iSeries and other DRDA-compliant database servers. A .NET client that uses DB2 .NET provider connects to a DRDA server job on iSeries. The iSeries DRDA server jobs are called QRWTSRVR and they run in the QUSRWRK subsystem. You can use the DB2 Configuration Assistant (db2ca) on the Windows client to configure the required DRDA connection to the iSeries.
Once the connection has been configured, you can take advantage of the DB2 Visual Studio add-in. The DB2 add-in provides a rich collection of functions that support DB2 servers directly in Visual Studio's IDE. These are the most important features:
- DB2 .NET Managed Provider
- Solution Explorer--create and manage DB2 projects
- IBM Explorer--view DB2 server catalog information and client side ADO.NET code generation
- SQL Editor--edit DB2 scripts (includes syntax colorization and statement auto-completion)
- DB2 Tools Toolbar
In addition, the DB2 add-in provides
wizards to visually configure DB2 commands, adapters, etc. For example, the DB2
Data Adapter Configuration wizard allows you to define SQL statements and stored
procedure calls. It also allows you to define the structure of the result set
and map SQL command parameters to columns in the result set.
To illustrate how a wizard simplifies the developer's work, assume that we decided to use the DB2DataAdapter in our DB2i5Access sample application. In this case, instead of programmatically defining the database connection, required SQL statements, and statements parameters, we drag a DB2DataAdapter object from the Visual Studio Toolbox and drop it on the form designer. This launches the configuration wizard. First, we provide the iSeries database connection information. Remember, the DRDA connection to the iSeries needs to exist at this time (use db2ca to configure it). Then, we specify what SQL commands are required by the adapter. In addition to the default SELECT, we choose INSERT, UPDATE, and DELETE. Next, we specify the SELECT statement as shown in Figure 3.
Figure 3: Define SQL statements with the DB2 Data Adapter Configuration
At this point, the wizard also allows you to validate the statement on the back-end iSeries database as well as provide the table to dataset mapping. Similarly, we define other SQL statements. We use the wizard's Generate function to automatically generate the statement text along with all required parameters. For example, the Generate function used on the UPDATE statement dialog generates the UPDATE statement that implements the optimistic locking. This is shown in Figure 4.
Figure 4: Generate an Update statement.
At the end of the configuration process, the wizard creates two objects associated with the VB Form class: db2Connection1 and DB2DataAdapter1. The wizard also generates the necessary code in the "Windows Form Designer generated code" region. The developer should not modify this code. The wizard should be used to make any changes in the DB2DataAdapter configuration.
Hopefully, this short walkthrough has given you a taste of the truly unique capabilities of the DB2 add-in. See DB2 Development Add-In Technical Preview for more information.
What Provider Is Right for You?
Choosing the appropriate .NET data provider for your
application depends on your environment. If you plan to access only the iSeries
database, the DB2 UDB for iSeries .NET provider should be your choice. This
managed provider will provide better performance than using the
System.Data.OleDb provider to bridge to the iSeries Access OLE DB provider or
using the Microsoft.Data.Odbc provider to bridge to the iSeries Access ODBC
Consider the DB2 UDB (LUW) provider if you need to access different DB2 UDB platforms, such as DB2 UDB for Windows or DB2 UDB for z/OS, in addition to iSeries or if you wish to take advantage of the GUI add-Ins discussed in the previous section.