Oracle® Objects for OLE C++ Class Library Developer's Guide 10g Release 2 (10.2) Part Number B14308-01 |
|
The Oracle C++ Class Library takes a somewhat different approach to binding than most other C++ database classes. Most classes favor an early binding approach. In this technique, you define classes that correspond to the query result set. Different columns in the result records are bound to data members of the class. The class is generally a subclass of a more general record class. With this approach, you define your data access when you are writing your code, often running a code-generating tool that accesses the database. As you navigate through the query result set, data is placed in every column as each row becomes current - implying a constant moving around of data, with data conversions from database types to native types. All this happens whether or not the code does, in fact, reference the data.
The lower levels of this library (ODatabase, ODynaset, OField) use a late binding approach. You do not need a tool to create classes at the time you write your code. No data is moved out of the record cache until the code asks for it. For instance, if a program never asks for the value of the third field in the current record, it is never translated from the database's data type. Data is obtained and set by method calls.
The difference between the two methods can be illustrated with an example like the one in A Simple Example. Here we expand that example: in addition to looking at salary data, we set everybody's commission.
An early binding scheme would work like this:
// Before compiling, run a tool to create the employee class:
class employee : public records {
public:
double salary;
double commission;
char *name;
}
// First, create an employee object:
employee theEmp;
// Use a method inherited from class records to get data,
// the database connection and SQL query being implicit:
theEmp.Query();
// Navigate to the first record:
theEmp.MoveFirst();
while (!theEmp.IsEOF())
{
// The Analyze method implicitly gets the salary:
Analyze(theEmp.salary);
// Initiate editing:
theEmp.StartEdit();
// Directly change commission value:
theEmp.commission = 500.0;
// Save the changes:
theEmp.Update();
theEmp.MoveNext();
}
Using just the ODynaset and ODatabase classes, we would have:
ODatabase theDB;
ODynaset theEmp;
double salary;
theDB.Open("ExampleDB", "scott", "tiger");
theEmp.Open(theDB, "select ename, sal, comm from employee");
while (!theEmp.IsEOF())
{
// Fetch the data:
theEmp.GetFieldValue("sal", &salary);
Analyze(salary);
// Initiate editing:
theEmp.StartEdit();
// Set the commission:
theEmp.SetFieldValue("comm", 500.0);
// Save the changes:
theEmp.Update();
theEmp.MoveNext();
}
The difference is that the late binding case does not specify either the database connection or the query until run time; the argument strings could have been constructed by the program. Instead, methods are called to connect to the database, to get values and to set values. In this way you avoid all the work of binding the employee name and its attendant memory management. Furthermore, when the update is executed, the late binding code needs to update only the changed fields in the database because it knows which fields have been changed. The early binding case must update all the fields, because it does not know which data members have been changed.
You can make the late binding code even more convenient by using OField objects:
ODatabase theDB("ExampleDB", "scott", "tiger");
ODynaset theEmp(theDB, "select ename, sal, comm from employee");
OField salary = theEmp.GetField("salary");
OField comm = theEmp.GetField("comm");
while (!theEmp.IsEOF())
{
// An implicit cast fetches the data from Ofield:
Analyze(salary);
theEmp.StartEdit();
// Set the bonus:
comm.SetValue(500.0);
theEmp.Update();
theEmp.MoveNext();
}
Because setting the commission calls a method (SetValue) on an OField object, the dynaset knows which field has been updated, and so updates only the changed field.
The OBinder and OBound classes provide support for more automatic binding. Use these classes to specify that an object is tied to a particular column's value. You then supply methods to be called when the value changes or when the bound object changes the value. Using these classes you could, for example, implement the equivalent of the early binding code. A very simple example follows:
OBinder binder;
OBoundTextEdit ename;
OBoundTextEdit sal;
// Set up binder object:
binder.Open("ExampleDB", "scott", "tiger", "select * from emp");
// Attach the bound edits to the user interface widgets;
// wnd is a framework pointer to the widget's parent window:
ename.BindToEdit(wnd, IDC_ENAME);
sal.BindToEdit(wnd, IDC_SAL);
// Attach the bound edits to the binder:
ename.BindToOBinder(&binder, "ename");
sal.BindToOBinder(&binder, "sal");
This works like early binding but the binding is not specified until runtime. The OBound subclass objects are attached to particular dynaset fields at runtime. From then on they will receive the current value of the field as the dynaset is navigated.
The binder object (m_binder) functions very much like the data control in Visual Basic. It is opened with the information needed to connect to a database (database name, username, password) and a SQL statement that retrieves a set of records. The bound objects (m_ename and m_sal) function very much like bound controls in Visual Basic. In this case they are bound textedits. Calls need to be made to associate the bound textedit with an item in a window (the BindToEdit call) and to associate the bound textedit with a field in a particular binder (BindToOBinder). For more information see the documentation for OBound and OBinder.