Sei sulla pagina 1di 16

Borland Data Provider (BDP) for the Microsoft.

NET
Framework
By: John Kaster

Abstract: High-performance enterprise database development for ADO.NET

Borland ® Data Provider (BDP) for the Microsoft®


.NET Framework
A Borland White Paper by Ramesh Theivendran, January 2004

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

BdpConnection: Connecting to the database


Namespace: Borland.Data.Provider.BdpConnection
public sealed class BdpConnection : Component, IDbConnection,
ICloneable

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.

ConnectionOption , another property, holds a name-value pair of database-specific connection- level


properties.

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";

BdpConnection Conn = new BdpConnection();


Conn.ConnectionString = ConnStr;
Conn.ConnectionOptions = ConnOptions;

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 = (BdpCommand) Conn.CreateCommand();


Comm.Connection = Conn;
Comm.Transaction = Trans;

----

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: Retrieving data


Namespace: Borland.Data.Provider.BdpDataReader
public sealed class BdpDataReader: MarshalByRefObject, IEnumerable,
IDataReader, IDisposable, IDataRecord

A BdpDataReader is returned as a result of a SELECT or stored procedure execution from


BdpCommand.ExecuteReader(). Because there is no public constructor, an instance of a BdpDataReader
cannot be instantiated. BdpDataReader provides a forward-only cursor and the associated cursor-level
metadata.

BdpDataReader methods such as GetName(), GetDataTypeName(), GetFieldType(), GetDataType(), and


GetDataSubType() provide the cursor-level metadata. For all of these methods, the ordinal of the column in
the cursor, which is zero based, must be passed. Given a column name, GetOrdinal() returns the column
ordinal or position in the select list. GetName(), GetDataTypeName(), and GetFieldType() return the name,
SQL datatype name, and the .NET Framework System.Type, respectively, for a particular column.
GetDataType() and GetDataSubType() return the BDP logical datatype and the
subtype, respectively. GetSchemaTable() also can be used to retrieve the metadata of the cursor as a
DataTable.

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.

public static void ExecuteCommand ( BdpCommand Comm )


{
Comm.CommandText = " SELECT * FROM EMPLOYEE";
Comm.Prepare();
BdpDataReader Reader = Comm.ExecuteReader();
if ( Reader != null )
{
while (Reader.Read())
{
ShowData(Reader);
}
Reader.Close();
}
Comm.Close();
}

public static void ShowData(BdpDataReader Reader)


{
long retVal = 0, startIndex = 0;
int buffSize = 0;
char []buffer = null;

for (Int32 index = 0; index < Reader.FieldCount; index++)


{
//Check for NULL
if ( Reader.IsDBNull(index) )
{
Console.Write("NULL");
}
else
{
Type t = Reader.GetFieldType(index);
if (t == typeof(Int16) )
Console.Write(Reader.GetInt16(index));
else if ( t == typeof(Int32) )
Console.Write(Reader.GetInt32(index));
else if ( t == typeof(String) )
Console.Write(Reader.GetString(index));
else if ( t == typeof(float) )
Console.Write(Reader.GetFloat(index));
else if ( t == typeof(double) )
Console.Write(Reader.GetDouble(index));
else if ( t == typeof(DateTime) )
Console.Write(Reader.GetDateTime(index));
else if ( t == typeof(Decimal) )
Console.Write(Reader.GetDecimal(index));
else if ( (t == typeof(Byte[])) || (t == typeof(Char[])) )
{
BdpType DataType = Reader.GetDataType(index);
if (DataType == BdpType.Blob)
{
retVal = Reader.GetChars(index, 0, null, 0, 0);
Console.Write("Blob Size = " + retVal);
buffSize = (int) retVal;

//Display only character blob data


if ( retVal > 0 && (t== typeof(Char[])) )
{
buffer = new char[buffSize];
startIndex = 0;
retVal = Reader.GetChars(index, startIndex,
buffer, 0, buffSize);
for (int i = 0; i < buffer.Length; i++ )
Console.Write(buffer[i]);
}

if (index < Reader.FieldCount -1)


Console.Write(", ");
}

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.

BdpParameter.Direction property by default is ParameterDirection.Input. In the case of stored procedures, it


can be set to Output, InputOutput, or ReturnValue . If the inout parameter is expected to return more data
than the input, BdpParameter.Precision should be specified to a size large enough to hold the output data.
Precision is a byte, and if values larger than 255 bytes must be returned, the MaxPrecision property must
be set. Note that not all databases support all the different parameter directions.

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.

BdpTransaction: Transaction control


Namespace: Borland.Data.Provider.BdpTransaction
public sealed class BdpTransaction : MarshalByRefObject,
IDbTransaction, IDisposable

BdpTransaction takes care of controlling a database transaction with respect to a connection.


BdpConnection.BeginTransaction() on an opened connection returns a new BdpTransaction object.
Commit() and Rollback() take care of committing or rolling back a transaction. For setting different isolation
levels, the IsolationLevel property can be used, and the default isolation is ReadCommitted . The current
implementation does not support multiple transactions on a single connection.

Following is a code snippet that shows runtime parameter binding and executing a stored procedure in the
context of a transaction

static void BindParameter ( BdpConnection Conn, Int32 Times )


{
Int32 Count = 0, Rows = 0;
BdpTransaction Trans = (BdpTransaction) Conn.BeginTransaction();

BdpCommand Comm = (BdpCommand) Conn.CreateCommand();


Comm.Connection = Conn;
Comm.Transaction = Trans;

Comm.CommandType = CommandType.StoredProcedure;
Comm.CommandText = "MYTESTPROC";
Comm.ParameterCount = 3;

Comm.Prepare();

BdpParameter param1 = Comm.Parameters.Add("P1",


DbType.StringFixedLength, 10);
BdpParameter param2 = Comm.Parameters.Add("P2",
DbType.String, 5);
param2.Direction = ParameterDirection.InputOutput;
param2.Precision = 25;
BdpParameter param3 = Comm.Parameters.Add("P3",
DbType.Decimal);
param3.Direction = ParameterDirection.Output;

while ( Count < Times )


{
param1.Value = "Record" + Count;
param2.Value = "Hello";
param3.Value = null;

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.

The BdpDataAdapter has InsertCommand, UpdateCommand, and DeleteCommand properties for


resolving DataSet changes back into the data source. A valid parameterized SQL statement for each of
these commands must be specified before the Update() method can be called. Based on the DataSet
changes, the Update() method executes INSERT, UPDATE, and DELETE commands and persists all the
changes to the data source.

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.

Like GetSchemaTable() method in BdpDataReader, the BdpDataAdapter also has a FillSchema()


method that creates a DataTable and configures the metadata to match with the database. Consequently, to
have integrity constraints such as primary key and unique key enforced in the DataSet, FillSchema() must
be called before Fill() is called. BdpDataAdapter.TableMappings allows table, column names to be mapped
from the data source to more meaningful names.

The following is a code snippet that shows providing and resolving data with a BdpDataAdapter.

static void FillDataAdapter ( BdpConnection Conn )


{
int Rows = 0;

BdpTransaction Trans = (BdpTransaction) Conn.BeginTransaction();

BdpDataAdapter adapter = new BdpDataAdapter();


BdpCommand Comm = new BdpCommand("SELECT * FROM TESTTABLE", Conn);
adapter.SelectCommand = Comm;

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();

static void InsertRecord(BdpConnection Conn, BdpDataAdapter adapter,


DataTable dataTable )
{
BdpCommand CommIns = new BdpCommand("INSERT INTO TESTTABLE
VALUES(?)", Conn);
BdpParameter param1 =
CommIns.Parameters.Add("FCHAR",DbType.StringFixedLength, 10);
param1.SourceColumn = "FCHAR";

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;

BdpTransaction Trans = (BdpTransaction) Conn.BeginTransaction();

BdpDataAdapter adapter = new BdpDataAdapter();


BdpCommand Comm = new BdpCommand("SELECT * FROM TESTTABLE", Conn);
adapter.SelectCommand = Comm;

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();

static void InsertRecord(BdpConnection Conn, BdpDataAdapter adapter,


DataTable dataTable )
{
BdpCommand CommIns = new BdpCommand("INSERT INTO TESTTABLE VALUES(?)",
Conn);
BdpParameter param1 =
CommIns.Parameters.Add("FCHAR",DbType.StringFixedLength, 10);
param1.SourceColumn = "FCHAR";

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

Command Text Editor


The Command Text Editor is a simplified version of a SQL builder capable of generating SQL for a single
table. Depending on the SchemaName property in the BdpCommand , the database objects are filtered,
and only tables on that schema are listed. If a SchemaName is not specified, all of the available objects for
the current login user are listed. The value of QuoteObjects in the ConnectionOptions determines whether
the objects will be quoted with the database-specific quote character or not. The current implementation
does not list available stored procedures.

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.

Finally, BdpCommand.CommandText is set to the SQL statement that is generated.


Figure 4: BDP Command Text Editor

Data Adapter Configuration


The Data Adapter Configuration is similar to the Command Text Editor, but it allows the generation of
SELECT, INSERT, UPDATE, and DELETE statements. After successful SQL generation, new
BdpCommand objects are created and added to the BdpDataAdapter SelectCommand,
DeleteCommand, InsertCommand, and UpdateCommand properties.
Figure 5: BDP Data Adapter Configuration dialog

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.

Potrebbero piacerti anche