Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
NET
Framework
By: John Kaster
Contents
• Introduction
• BDP architecture
• BDP components
o BdpConnection: Connecting to the database
o BdpCommand: Executing SQL or stored procedure
o BdpDataReader: Retrieving data
o BdpParameter: Runtime parameter binding
o BdpTransaction: Transaction control
o BdpDataAdapter: Providing and resolving data
o BDP component designers
Connections Editor
Command Text Editor
Data Adapter Configuration
o Data Explorer
• Conclusion
Introduction
When Microsoft released the .NET Framework, it introduced many new technologies, including a new data-
access model, ADO.NET. ADO.NET is a disconnected, n-tier data-access model with better integration with
XML and XSD. ADO.NET has two core components: .NET Data Provider and DataSet. The Data Provider
defines a set of interfaces for data access and provides and resolves data to and from a DataSet. DataSet
represents relational data in memory from a data source. The data source can be either a Relational
Database Management System (RDBMS) or an XML file.
This paper introduces the Borland ® Data Provider (BDP) for the Microsoft® .NET Framework included in
Borland rapid application development (RAD) tools, such as the newly released Borland ® C#Buildertm - a
pure C# development solution for .NET. BDP is a .NET data provider implementing the Microsoft .NET Data
Provider core interfaces. BDP provides a set of Common Language Runtime (CLR) components that allows
data access from one of the BDP supported databases. BDP also comes with a rich set of component
designers and tools that make database development easier.
BDP architecture
BDP is an open architecture that exposes a set of interfaces for third-party integration. One can implement
these interfaces for their own database and provide design-time, tools, and runtime data-access integration
into the Borland ® C#Builder IDE. BDP-managed components talk to these interfaces for all basic data-
access functionalities. The implementation of these interfaces wraps database-specific native client libraries
through platform Invoke (PInvoke) services. Depending on the availability of managed database clients, one
can have a fully managed provider implemented underneath BDP.
Figure 1: BDP components, designers, and tools talking to core BDP interfaces
The database-specific implementation is wrapped into an assembly, and the full name of the assembly is
passed to the BdpConnection component as part of the connection string. Depending on the Assembly
entry specified in the BdpConnection.ConnectionString property, BDP dynamically loads the database-
specific provider and consumes the implementation for ISQLConnection, ISQLCommand, and
ISQLCursor . This allows applications to be switched from one database to another just by changing the
ConnectionString property to a different provider implementation.
BDP maps SQL datatypes to .NET Framework datatypes, avoiding the need to learn a database-specific
type system and making it easier for .NET developers to do database development. Also, whenever
possible, an attempt is made to have consistent datatype mapping across databases. So, with BDP, one can
write a single set of source that can be used to talk with multiple databases. Today, one can achieve the
same with the .NET Framework data providers by talking to their interfaces directly and using untyped
accessors. However, an application becomes less portable once strongly typed accessors are used. BDP by
default does not support any database-specific typed accessors.
BDP components expose optional property strings that can be used to surface database- specific features.
BDP is currently available only on Microsoft ® Windows,® but, depending on the availability of the C#
compiler and CLR on other platforms, BDP components can be ported there as well.
BDP components
The .NET Framework 1.1 ships with five different data providers: System.Data.SQLClient,
System.Data.SQLServerCe, System.Data.Oledb, System.Data.Odbc, and System.Data.OracleClient. The
BDP components, metadata access, and designers are defined under the following namespaces:
Borland.Data.Provider, Borland.Data.Common, Borland.Data.Schema, and Borland.Data.Design. BDP
currently supports Borland ® InterBase,® Oracle,® IBM® DB2,® and Microsoft® SQL Serverd 2000.
Figure 2: ADO.NET data provider architecture
To establish a database connection, a new BdpConnection must be created and its ConnectionString
property set. ConnectionString is a name-value pair of all the parameters needed to connect to a particular
database. The ConnectionString is used as an identifier for creating a connection pool. The current
implementation of BDP does not support connection pooling.
BdpConnection has Open() and Close() methods to connect and disconnect from a database. Open()
uses the connection parameters specified in the ConnectionString to establish a connection to a database.
For every Open(), there should be a corresponding Close() connection. Otherwise, subsequent connection
attempts will fail with an exception.
A valid connection must be opened before the BdpConnection can be used to create a new
BdpCommand by calling CreateCommand(), start a new transaction by calling BeginTransaction(),
change database by calling ChangeDatabase(), get access to the ISQLMetadata or ISQLResolver by
calling GetMetaData() or GetResolver(), respectively.
ISQLMetadata and ISQLResolver are BDP extensions to ADO.NET data providers to simplify schema
retrieval and resolver SQL generation.
The following is a code snippet that shows ConnectionString and ConnectionOptions to connect to
InterBase.
using System;
using System.Data;
using Borland.Data.Provider;
using Borland.Data.Common;
class test
{
static void Main()
{
ConnectionState state;
BdpTransaction Trans = null;
BdpCommand Comm = null;
String ConnStr = "Provider=interbase;"
+ "Assembly=Borland.Data.Interbase,Version=1.1.0.0,"
+ "Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b;"
+
"Database=localhost:c:\\IB7\\examples\\database\\employee.gdb;"
+ "UserName=sysdba;Password=masterkey";
String ConnOptions =
"LoginPrompt=true;SQLDialect=3;RoleName=Vina;"
+
"CommitRetain=true;ServerCharSet=UNICODE_FSS;RollbackRetain=False;"
+ "TxnIsolation=ReadCommitted;WaitOnLocks=true";
try
{
Conn.Open();
}
catch ( BdpException e)
{
for (int j= 0; j < e.Errors.Count; j++)
{
Console.WriteLine(e.Errors[j].Message);
}
return;
}
Trans = (BdpTransaction)Conn.BeginTransaction();
----
Comm.Close();
Trans.Commit();
Conn.Close();
}
}
BdpCommand: Executing SQL or stored procedure
Namespace: Borland.Data.Provider.BdpCommand
public sealed class BdpCommand : Component, IDbCommand, ICloneable
BdpCommand provides a set of methods and properties for SQL and stored procedure execution. The
CommandType property specifies whether a SQL statement or a table name or a stored procedure name is
being used in the CommandText property. Before a SQL statement can be executed, the Connection
property should be set to a valid BdpConnection. To execute a SQL statement in the context of a
transaction, the Transaction property must be set to a valid BdpTransaction.
Depending on the type of SQL statement being executed, one of the following methods can be used:
ExecuteScalar, ExecuteReader, or ExecuteNonQuery. ExecuteNonQuery(), as the name implies, is for
executing DDL or non-SELECT DML statements or stored procedures that return no cursor. On successful
execution of a DML, ExecuteNonQuery() returns the number of rows affected on the database.
ExecuteReader() is used for SELECT statements and stored procedures that return a cursor or multiple
cursors. A new BdpDataReader is returned if ExecuteReader() successfully processes the SQL statement.
ExecuteScalar() is similar to ExecuteReader() , but it returns only the first column of the first record as an
object and is mainly used for executing SQL to get aggregate values.
For parameterized SQL execution, CommandText can be specified with parameter markers. BDP uses "?"
for parameter markers. In the current implementation, there is no support for named parameters. The
ParameterCount property specifies the number of parameter markers in the CommandText . During
preparation, the database-specific BDP provider implementation takes care of converting the "?" parameter
markers to database- specific parameter markers and also takes care of generating the appropriate SQL for
calling a stored procedure.
When executing the same SQL repeatedly, it is optimal to call Prepare() once and bind parameters.
Parameters are specified by adding a BdpParameterCollection to the BdpCommand.Parameters property.
Preparing a parameterized SQL statement on most databases creates an execution plan on the server that
will be used during subsequent execution of the same SQL with different parameter values.
Once a BdpCommand is done, calling Close() frees all the statement resources allocated by the provider.
BdpCommand.CommandOptions is for passing optional command-level properties.
BdpDataReader.Read() can be called to fetch records one after the other until a false is returned, which
indicates that one is at EOF. Before accessing individual column values, one can check if the data is NULL
by calling IsDBNull(). Then, depending on the datatype, one of the field accessor methods, such as
GetInt16(), GetInt32(), and GetFloat() , to name a few, can be called. BLOB data can be accessed as a
byte array or a character array by calling GetBytes() or GetChars() . A null buffer passed to these methods
returns the size of the BLOB data available. The current implementation of BdpDataReader does not
support fetching BLOB data by specifying offsets.
The initial state of the BdpDataReader returned from BdpCommand.ExecuteReader() is open. Once all of
the records have been fetched, the Close() can be called to free all of the cursor-related resources. To find
out if a BdpDataReader is closed, the IsClosed property can be checked. If the BdpDataReader is closed,
it will return true otherwise false. If CommandBehaviour.CloseConnection is specified in ExecuteReader(),
the BdpConnection used for executing the SQL is also closed while the BdpDataReader is closed.
NextResult() returns true if more results are available from a stored procedure execution.
The following is a code snippet that shows retrieving data using a BdpDataReader, assuming that a valid
connection and command are ready.
Console.WriteLine();
BdpParameter: Runtime parameter binding
Namespace: Borland.Data.Common.BdpParameter,
Borland.Data.Common.BdpParameterCollection
public sealed class BdpParameter: MarshalByRefObject, IDbDataParameter,
IDataParameter, ICloneable
public sealed class BdpParameterCollection: MarshalByRefObject,
IDataParameterCollection, IList, ICollection, IEnumerable
To pass runtime parameters for a parameterized SQL statement or stored procedure, the
BdpParameterCollection class can be used. An empty BdpParameterCollection is returned by the
BdpCommand.Parameters property. After successfully preparing the command, parameters are added to
the BdpParameterCollection by calling Add() and passing the parameter information such as name,
datatype, precision, scale, size, etc. One of the overloaded Add() methods available can be used, or
individual BdpParameter properties such as Direction, Precision, Scale, DbType, BdpType,
BdpSubType, Size, and MaxPrecison can be set. Parameter names must be unique, and the parameters
must be added to the BdpParameter collection in the same order in which parameters markers appear in
the SQL. This limitation will be removed once there is support for named parameters.
While specifying the parameter datatype, either System.Type or the BDP logical type and the subtype can
be used. BdpParameter.Value should be set with the runtime value for all parameters before executing the
command. After successful execution, output data is available in the Value property.
Following is a code snippet that shows runtime parameter binding and executing a stored procedure in the
context of a transaction
Comm.CommandType = CommandType.StoredProcedure;
Comm.CommandText = "MYTESTPROC";
Comm.ParameterCount = 3;
Comm.Prepare();
Rows = Comm.ExecuteNonQuery();
Console.WriteLine("Output param from MYTESTPROC= " +
param2.Value );
Console.WriteLine("InputOutput param from MYTESTPROC= " +
param3.Value );
Count ++;
Comm.Close();
Trans.Commit();
}
BdpDataAdapter: Providing and resolving data
Namespace: Borland.Data.Provider.BdpDataAdapter
public sealed class BdpDataAdapter : DbDataAdapter, IDbDataAdapter,
IsupportInitialize
BdpDataAdapter acts as a conduit between the data source and the .NET DataSet. It provides data from a
data source to the DataSet and resolves DataSet changes back to the data source. The BdpDataAdapter
has a DataSet property, and this DataSet gets automatically filled with data when the Active property on the
BdpDataAdapter is set to True.
The BdpDataAdapter uses the SelectCommand to provide data to a DataSet when the Fill() method is
called. The BdpConnection associated with the SelectCommand is used to execute the command
specified in the SelectCommand.CommandText. If the BdpConnection is already opened, it is used;
otherwise, Fill takes care of opening the connection, executing the SQL statement, and retrieving the result
set, and then closes the connection. While working with large result sets, the number of records that will be
populated into the DataSet can be controlled by using the MaxRecords and the StartRecord properties.
BdpDataAdapter.AutoUpdate() method lets data be resolved automatically without the need to specify all the
SQL. AutoUpdate() in turn uses a BdpCommandBuilder to generate updates, delete, and insert SQL.
Although using AutoUpdate() makes things look simpler, the current implementation does not generate
optimal SQL every time. Also, it does not handle master-detail updates. Until these issues are addressed, be
aware that simplicity is achieved at the cost of performance.
For resolving data from a stored procedure or a complex SQL such as joins, AutoUpdate() cannot be used.
In these cases, the BdpDataAdapter DeleteCommand, UpdateCommand, and InsertCommand should
be explicitly specified and the Update() method should be used.
The following is a code snippet that shows providing and resolving data with a BdpDataAdapter.
DataTableMapping dataTabMap =
adapter.TableMappings.Add("Table1","TESTTABLE");
DataSet ds = new DataSet();
adapter.FillSchema(ds, SchemaType.Source, "TESTTABLE");
Rows = adapter.Fill(ds, "TESTTABLE");
Trans.Commit();
adapter.InsertCommand = CommIns;
//Insert 10 records
for ( int i=0; i < 10; i++)
{
DataRow newRow = dataTable.NewRow();
newRow["FCHAR"] = "VINA" + i;
dataTable.Rows.Add(newRow);
}
}
static void FillDataAdapter ( BdpConnection Conn )
{
int Rows = 0;
DataTableMapping dataTabMap =
adapter.TableMappings.Add("Table1","TESTTABLE");
DataSet ds = new DataSet();
adapter.FillSchema(ds, SchemaType.Source, "TESTTABLE");
Rows = adapter.Fill(ds, "TESTTABLE");
InsertRecord(Conn, adapter, ds.Tables["TESTTABLE"]);
adapter.Update(ds,"TESTTABLE");
ds.AcceptChanges();
Trans.Commit();
adapter.InsertCommand = CommIns;
//Insert 10 records
for ( int i=0; i < 10; i++)
{
DataRow newRow = dataTable.NewRow();
newRow["FCHAR"] = "VINA" + i;
dataTable.Rows.Add(newRow);
}
}
BDP component designers
BDP components come with a rich set of designers like the Connections Editor, the Command Text Editor,
and the Data Adapter Configuration dialog. These designers can be accessed by right-clicking on the
components or clicking on the designer verbs associated with the components.
Connections Editor
The Connections Editor manages connection strings and database-specific connection properties. With the
Connections Editor, connections can be added, removed, deleted, renamed, and tested. Changes to the
connection information are then persisted into BdpConnections.xml. Once a particular connection is chosen,
the designer generates the connection string and the connection options, and assigns them to
BdpConnection.ConnectionString and BdpConnection.ConnectionOptions properties, respectively.
Figure 3: BDP Connections Editor showing connection information in a Property Grid
The Command Text Editor allows a table name from a list of available tables and columns from a list of
columns for a particular table to be chosen. Using this information, it generates a SQL statement. To
generate the SQL, the designer uses a BdpCommandBuilder . When optimized SQL is requested, index
information is used to generate the "where" clause for SELECT, UPDATE, and DELETE statements;
otherwise, non-BLOB columns and searchable columns form the "where" clause.
After successful SELECT SQL generation, data can be previewed and a new DataSet generated, or an
existing DataSet can be used to populate a new DataTable. If a new DataSet is created, it is automatically
added to the designer host. Once a BdpDataAdapter is configured, a DataGrid can be hooked up and its
DataSource and DataMember properties set to see design-time data. BdpDataAdapter also has a
designer verb for typed DataSet generation.
Figure 6: Preview Data for the SQL statement generated by the BDP resolver
Data Explorer
Data Explorer uses the ISQLDataSources interface to get a list of available providers, database
connections, and schema objects that are supported by different providers. The list of available providers is
persisted in BdpDataSources.xml, and the available connections in BdpConnections.xml. Once a provider is
chosen, the ISQLMetadata interface is used to retrieve metadata and display a read-only tree view of
database objects. The current implementation provides a list of Tables, Views, and Stored Procedures for all
BDP-supported databases.
Data Explorer is integrated into the C#Builder IDE and also is available as a standalone executable.
Figure 7: Data Explorer for browsing database objects
Conclusion
BDP provides a generic set of classes for data-access in .NET. It also makes third-party integration into the
C#Builder IDE easier and provides features for schema retrieval and resolver SQL generation. As database
vendors come with fully managed clients for .NET, the BDP providers can also evolve to be fully managed.
Future versions of BDP are scheduled to have designer, tools, and runtime enhancements to make .NET
database application development even easier.
Made in Borland ® Copyright © 2004 Borland Software Corporation. All rights reserved. All Borland brand
and product names are trademarks or registered trademarks of Borland Software Corporation in the United
States and other countries. Microsoft, Windows, and other Microsoft product names are trademarks or
registered trademarks of Microsoft Corporation in the U.S. and other countries. All other marks are the
property of their respective owners. Corporate Headquarters: 100 Enterprise Way, Scotts Valley, CA 95066-
3249 § 831-431-1000 § www.borland.com § Offices in: Australia, Brazil, Canada, China, Czech Republic,
Finland, France, Germany, Hong Kong, Hungary, India, Ireland, Italy, Japan, Korea, Mexico, the
Netherlands, New Zealand, Russia, Singapore, Spain, Sweden, Taiwan, the United Kingdom, and the
United States. § 21137
Note! You can also download this document as a PDF from CodeCentral.