Borland Data Provider

Embed Size (px)

Citation preview

  • 8/7/2019 Borland Data Provider

    1/16

    Borland Data Provider (BDP) for the Microsoft.NETFramework

    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 componentso BdpConnection: Connecting to the databaseo BdpCommand: Executing SQL or stored procedureo BdpDataReader: Retrieving datao BdpParameter: Runtime parameter bindingo BdpTransaction: Transaction controlo BdpDataAdapter: Providing and resolving datao BDP component designers

    Connections Editor

    Command Text Editor

    Data Adapter Configurationo 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 withXML and XSD. ADO.NET has two core components: .NET Data Provider and DataSet. The Data Providerdefines a set of interfaces for data access and provides and resolves data to and from a DataSet. DataSetrepresents relational data in memory from a data source. The data source can be either a RelationalDatabase Management System (RDBMS) or an XML file.

    This paper introduces the Borland Data Provider (BDP) for the Microsoft .NET Framework included inBorland rapid application development (RAD) tools, such as the newly released Borland C#Buildertm - apure 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 allowsdata access from one of the BDP supported databases. BDP also comes with a rich set of componentdesigners 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 implementthese interfaces for their own database and provide design-time, tools, and runtime data-access integrationinto the Borland C#Builder IDE. BDP-managed components talk to these interfaces for all basic data-

    http://gp.embarcadero.com/authors/edit/119.aspxhttp://gp.embarcadero.com/authors/edit/119.aspxhttp://edn.embarcadero.com/article/31939#intro%23introhttp://edn.embarcadero.com/article/31939#bdparch%23bdparchhttp://edn.embarcadero.com/article/31939#bdpcomps%23bdpcompshttp://edn.embarcadero.com/article/31939#bdpconnection%23bdpconnectionhttp://edn.embarcadero.com/article/31939#bdpcommand%23bdpcommandhttp://edn.embarcadero.com/article/31939#bdpdatareader%23bdpdatareaderhttp://edn.embarcadero.com/article/31939#bdpparameter%23bdpparameterhttp://edn.embarcadero.com/article/31939#bdptransaction%23bdptransactionhttp://edn.embarcadero.com/article/31939#bdptransaction%23bdptransactionhttp://edn.embarcadero.com/article/31939#bdpdataadapter%23bdpdataadapterhttp://edn.embarcadero.com/article/31939#designers%23designershttp://edn.embarcadero.com/article/31939#connedit%23connedithttp://edn.embarcadero.com/article/31939#cmdtext%23cmdtexthttp://edn.embarcadero.com/article/31939#dataadapter%23dataadapterhttp://edn.embarcadero.com/article/31939#dataexp%23dataexphttp://edn.embarcadero.com/article/31939#conclusion%23conclusionhttp://gp.embarcadero.com/authors/edit/119.aspxhttp://edn.embarcadero.com/article/31939#intro%23introhttp://edn.embarcadero.com/article/31939#bdparch%23bdparchhttp://edn.embarcadero.com/article/31939#bdpcomps%23bdpcompshttp://edn.embarcadero.com/article/31939#bdpconnection%23bdpconnectionhttp://edn.embarcadero.com/article/31939#bdpcommand%23bdpcommandhttp://edn.embarcadero.com/article/31939#bdpdatareader%23bdpdatareaderhttp://edn.embarcadero.com/article/31939#bdpparameter%23bdpparameterhttp://edn.embarcadero.com/article/31939#bdptransaction%23bdptransactionhttp://edn.embarcadero.com/article/31939#bdpdataadapter%23bdpdataadapterhttp://edn.embarcadero.com/article/31939#designers%23designershttp://edn.embarcadero.com/article/31939#connedit%23connedithttp://edn.embarcadero.com/article/31939#cmdtext%23cmdtexthttp://edn.embarcadero.com/article/31939#dataadapter%23dataadapterhttp://edn.embarcadero.com/article/31939#dataexp%23dataexphttp://edn.embarcadero.com/article/31939#conclusion%23conclusion
  • 8/7/2019 Borland Data Provider

    2/16

    access functionalities. The implementation of these interfaces wraps database-specific native client librariesthrough platform Invoke (PInvoke) services. Depending on the availability of managed database clients, onecan 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 ispassed to the BdpConnection component as part of the connection string. Depending on the Assemblyentry specified in the BdpConnection.ConnectionString property, BDP dynamically loads the database-specific provider and consumes the implementation forISQLConnection, ISQLCommand, andISQLCursor. This allows applications to be switched from one database to another just by changing theConnectionString property to a different provider implementation.

    BDP maps SQL datatypes to .NET Framework datatypes, avoiding the need to learn a database-specifictype system and making it easier for .NET developers to do database development. Also, wheneverpossible, an attempt is made to have consistent datatype mapping across databases. So, with BDP, one canwrite a single set of source that can be used to talk with multiple databases. Today, one can achieve thesame with the .NET Framework data providers by talking to their interfaces directly and using untypedaccessors. However, an application becomes less portable once strongly typed accessors are used. BDP bydefault 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. TheBDP components, metadata access, and designers are defined under the following namespaces:Borland.Data.Provider, Borland.Data.Common, Borland.Data.Schema, and Borland.Data.Design. BDPcurrently supports Borland InterBase, Oracle, IBM DB2, and Microsoft SQL Serverd 2000.

  • 8/7/2019 Borland Data Provider

    3/16

    Figure 2:ADO.NET data provider architecture

    BdpConnection: Connecting to the database

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

    To establish a database connection, a new BdpConnection must be created and its ConnectionStringproperty set. ConnectionString is a name-value pair of all the parameters needed to connect to a particulardatabase. The ConnectionString is used as an identifier for creating a connection pool. The currentimplementation of BDP does not support connection pooling.

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

    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 connectionattempts will fail with an exception.

    A valid connection must be opened before the BdpConnection can be used to create a newBdpCommand by calling CreateCommand(), start a new transaction by calling BeginTransaction(),change database by calling ChangeDatabase(), get access to the ISQLMetadata orISQLResolverbycalling GetMetaData() orGetResolver(), respectively.

    ISQLMetadata and ISQLResolver are BDP extensions to ADO.NET data providers to simplify schemaretrieval and resolver SQL generation.

    The following is a code snippet that shows ConnectionString and ConnectionOptions to connect toInterBase.

    using System;using System.Data;

  • 8/7/2019 Borland Data Provider

    4/16

  • 8/7/2019 Borland Data Provider

    5/16

    Namespace: Borland.Data.Provider.BdpCommandpublic sealed class BdpCommand : Component, IDbCommand, ICloneable

    BdpCommand provides a set of methods and properties for SQL and stored procedure execution. TheCommandType property specifies whether a SQL statement or a table name or a stored procedure name isbeing used in the CommandText property. Before a SQL statement can be executed, the Connectionproperty 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, orExecuteNonQuery. ExecuteNonQuery(), as the name implies, is forexecuting DDL ornon-SELECT DML statements or stored procedures that return no cursor. On successfulexecution of a DML, ExecuteNonQuery() returns the number of rows affected on the database.ExecuteReader() is used forSELECT statements and stored procedures that return a cursor or multiplecursors. A new BdpDataReaderis returned ifExecuteReader() successfully processes the SQL statement.ExecuteScalar() is similar to ExecuteReader() , but it returns only the first column of the first record as anobject 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. TheParameterCount property specifies the number of parameter markers in the CommandText . During

    preparation, the database-specific BDP provider implementation takes care of converting the "?" parametermarkers to database- specific parameter markers and also takes care of generating the appropriate SQL forcalling 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 thatwill 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.BdpDataReaderpublic sealed class BdpDataReader: MarshalByRefObject, IEnumerable,IDataReader, IDisposable, IDataRecord

    A BdpDataReaderis returned as a result of a SELECT or stored procedure execution fromBdpCommand.ExecuteReader(). Because there is no public constructor, an instance of a BdpDataReadercannot be instantiated. BdpDataReaderprovides a forward-only cursor and the associated cursor-levelmetadata.

    BdpDataReadermethods such as GetName(), GetDataTypeName(), GetFieldType(), GetDataType(), andGetDataSubType() provide the cursor-level metadata. For all of these methods, the ordinal of the column inthe cursor, which is zero based, must be passed. Given a column name, GetOrdinal() returns the columnordinal 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 thesubtype, respectively. GetSchemaTable() also can be used to retrieve the metadata of the cursor as aDataTable.

    BdpDataReader.Read() can be called to fetch records one after the other until a false is returned, whichindicates that one is at EOF. Before accessing individual column values, one can check if the data is NULLby calling IsDBNull(). Then, depending on the datatype, one of the field accessor methods, such asGetInt16(), GetInt32(), and GetFloat() , to name a few, can be called. BLOB data can be accessed as abyte array or a character array by calling GetBytes() orGetChars() . A null buffer passed to these methods

  • 8/7/2019 Borland Data Provider

    6/16

  • 8/7/2019 Borland Data Provider

    7/16

    elseif ( t == typeof(DateTime) )Console.Write(Reader.GetDateTime(index));

    elseif ( t == typeof(Decimal) )Console.Write(Reader.GetDecimal(index));

    elseif ( (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 = newchar[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.BdpParameterCollectionpublic sealed class BdpParameter: MarshalByRefObject, IDbDataParameter,IDataParameter, ICloneablepublic sealed class BdpParameterCollection: MarshalByRefObject,IDataParameterCollection, IList, ICollection, IEnumerable

    To pass runtime parameters for a parameterized SQL statement or stored procedure, theBdpParameterCollection class can be used. An empty BdpParameterCollection is returned by theBdpCommand.Parameters property. After successfully preparing the command, parameters are added tothe 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, orindividual BdpParameterproperties such as Direction, Precision, Scale, DbType, BdpType,BdpSubType, Size, and MaxPrecison can be set. Parameter names must be unique, and the parametersmust be added to the BdpParametercollection in the same order in which parameters markers appear inthe 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, itcan be set to Output, InputOutput, orReturnValue . If the inout parameter is expected to return more datathan the input, BdpParameter.Precision should be specified to a size large enough to hold the output data.

  • 8/7/2019 Borland Data Provider

    8/16

    Precision is a byte, and if values larger than 255 bytes must be returned, the MaxPrecision property mustbe 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 canbe used. BdpParameter.Value should be set with the runtime value for all parameters before executing thecommand. After successful execution, output data is available in the Value property.

    BdpTransaction: Transaction control

    Namespace: Borland.Data.Provider.BdpTransactionpublic 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 isolationlevels, the IsolationLevel property can be used, and the default isolation is ReadCommitted . The currentimplementation 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

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

  • 8/7/2019 Borland Data Provider

    9/16

    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.BdpDataAdapterpublic sealed class BdpDataAdapter : DbDataAdapter, IDbDataAdapter,IsupportInitialize

    BdpDataAdapteracts as a conduit between the data source and the .NET DataSet. It provides data from adata source to the DataSet and resolves DataSet changes back to the data source. The BdpDataAdapterhas a DataSet property, and this DataSet gets automatically filled with data when the Active property on theBdpDataAdapteris set to True.

    The BdpDataAdapteruses the SelectCommand to provide data to a DataSet when the Fill() method iscalled. The BdpConnection associated with the SelectCommand is used to execute the commandspecified 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 resultset, and then closes the connection. While working with large result sets, the number of records that will bepopulated into the DataSet can be controlled by using the MaxRecords and the StartRecord properties.

    The BdpDataAdapterhas InsertCommand, UpdateCommand, and DeleteCommand properties forresolving DataSet changes back into the data source. A valid parameterized SQL statement for each ofthese commands must be specified before the Update() method can be called. Based on the DataSetchanges, 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 theSQL. AutoUpdate() in turn uses a BdpCommandBuilderto generate updates, delete, and insert SQL.Although using AutoUpdate() makes things look simpler, the current implementation does not generateoptimal SQL every time. Also, it does not handle master-detail updates. Until these issues are addressed, beaware 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 shouldbe explicitly specified and the Update() method should be used.

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

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

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

    staticvoid FillDataAdapter ( BdpConnection Conn ){

  • 8/7/2019 Borland Data Provider

    10/16

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

    }

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

    }

    }staticvoid 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");

  • 8/7/2019 Borland Data Provider

    11/16

    InsertRecord(Conn, adapter, ds.Tables["TESTTABLE"]);adapter.Update(ds,"TESTTABLE");ds.AcceptChanges();

    Trans.Commit();

    }

    staticvoid 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 recordsfor ( 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 theConnections Editor, connections can be added, removed, deleted, renamed, and tested. Changes to theconnection 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 toBdpConnection.ConnectionString and BdpConnection.ConnectionOptions properties, respectively.

  • 8/7/2019 Borland Data Provider

    12/16

    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 singletable. 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 forthe current login user are listed. The value ofQuoteObjects in the ConnectionOptions determines whetherthe objects will be quoted with the database-specific quote character or not. The current implementationdoes not list available stored procedures.

    The Command Text Editor allows a table name from a list of available tables and columns from a list ofcolumns for a particular table to be chosen. Using this information, it generates a SQL statement. Togenerate the SQL, the designer uses a BdpCommandBuilder. When optimized SQL is requested, indexinformation is used to generate the "where" clause forSELECT, 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.

  • 8/7/2019 Borland Data Provider

    13/16

    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 ofSELECT, INSERT, UPDATE, and DELETE statements. After successful SQL generation, newBdpCommand objects are created and added to the BdpDataAdapterSelectCommand,DeleteCommand, InsertCommand, and UpdateCommand properties.

  • 8/7/2019 Borland Data Provider

    14/16

    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 automaticallyadded to the designer host. Once a BdpDataAdapteris configured, a DataGrid can be hooked up and itsDataSource and DataMemberproperties set to see design-time data. BdpDataAdapteralso has adesigner verb for typed DataSet generation.

  • 8/7/2019 Borland Data Provider

    15/16

  • 8/7/2019 Borland Data Provider

    16/16

    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 theC#Builder IDE easier and provides features for schema retrieval and resolver SQL generation. As databasevendors 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 .NETdatabase 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 UnitedStates and other countries. Microsoft, Windows, and other Microsoft product names are trademarks orregistered trademarks of Microsoft Corporation in the U.S. and other countries. All other marks are theproperty 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, theNetherlands, New Zealand, Russia, Singapore, Spain, Sweden, Taiwan, the United Kingdom, and theUnited States. 21137

    Note! You can also downloadthis document as a PDF fromCodeCentral.

    http://cc.borland.com/codecentral/ccweb.exe/listing?id=21374http://cc.borland.com/codecentral/ccweb.exe/listing?id=21374http://cc.borland.com/http://cc.borland.com/http://cc.borland.com/codecentral/ccweb.exe/listing?id=21374http://cc.borland.com/