Turbo Delphi Interbase Tutorial
Turbo Delphi Interbase Tutorial
Turbo Delphi Interbase Tutorial
COLIBRI.
abstract : developing database applications, using Turbo Delphi
.Net and Interbase
key words : Turbo Delphi - Interbase - ADO .Net - Windows
Forms
software used : Windows XP, Turbo Delphi
hardware used : Pentium 1.400Mhz, 256 M memory, 160 G
hard disc
scope : Delphi 8, Delphi 2005, Delphi 2006, Turbo Delphi for
Net
level : Delphi developer, Windows .NET developer
plan :
o Turbo Delphi and Interbase
o ADO.NET Architecture
o ADO.NET Programming
o Download the Sources
We will present:
This tutorial will not cover all aspects of Interbase development. We will not
talk about:
stored procedures, triggers
transactions and handling concurrency
the detail of visual .NET controls (TreeView, ListView etc)
Usually the Server and the Clients are on different PCs, and communication
between them is handled by network layers (TCP/IP or other).
If that does not work, use GOOGLE do locate the Colibri Interbase tutorial
paper which explains everything in detail, with snapshots and tests for every
step.
The .NET Framework comes bundled with data access assemblies for
handling SQL Server and Oracle, and Delphi installs the BDP. So nothing else
is required.
3 - ADO.NET Architecture
We will present several Delphi projects that:
Before using the data access components, let us first present the global
ADO.NET architecture.
the first layer directly talks to the data base and handles connection as
well as request forwarding. These components are collectively called
the DataProvider
and:
o BdpConnection connects to the Server (address, user name,
password etc)
o BdpCommand is used to send requests
(SELECT, CREATE, INSERT etc)
o SqlDataReader is used to fetch the result of SELECT requests.
The rows received from the Server can be handled by our code
(for computation or display purposes)
an intermediate component transfers the data between
the DataProvider and the components which store the data in memory:
it is the BdpDataAdapter. It contains:
o four BdpCommand components, one for each possible SQL
request: SELECT, INSERT, DELETE, UPDATE
o a TableMappings component, allowing to replace the syntactic
table names (Table1, Table2, ...) with semantic names (Invoice,
Orders, ...)
the Table rows can be stored and handled in memory. Those rows are
provided:
o by the BdpDataAdapter
o by reading a disc file (XML for example)
o by Delphi code
The memory storage components are called DataSet, and include:
o a collection of DataTables
o constraints (NOT NULL etc)
o relations (foreign keys, cascading rules etc)
o views, used for filtering rows, projecting columns, sorting,
aggregate computation, searching etc
finally, to display the rows and let the user modify them, we have
o controls, such as TextBox, ListBox or DataGrid
o DataBindings components which synchronize the modifications
between the visual controls and in memory data
BdpDataAdapter.Fill(DataSet)
The above figure show that the only Server dependent part is
the DataProvider. In our case, which DataProvider should we use to
handle SQL Server tables ?
4 - ADO.NET Programming
4.1 - Tasks
Our Interbase parameters (specified when we installed SQL Server) are the
default ones:
user: SYSDBA
password: masterkey
You will use your own Interbase parameters to establish the connection.
C:\programs\us\db\interbase\_data\ib_order_entry.gdb
and an alias
ib_order_entry
Click "Ok"
the Database files are created:
Close IbConsole
From now on, we will be able to perform all tasks from Turbo Delphi.
We will first create a new connection entry for our new database in
the Data Explorer
The Delphi IDE presents in the top right corner a notebook with a "Data
Explorer" tab. The explorer contains a TreeView with all possible drivers,
including Interbase, Oracle, MSSQL:
We can add to the "Interbase" item an entry for our new IB_ORDER_ENTRY
Database:
ib_order_entry_connection
enter the database name, the host name, the user name and the password
click "Test" to check the connection
the connection succeeds:
Our first test was with the Database Explorer, which is a separate utility
implemented in its own .EXE. We will now build our own Delphi Projects
which will be able to establish a connection to the newly created Database
to check the parameters, click the "connection_editor" link, located at the bottom
of the Object Inspector (pointed by the red arrow) or alternately, right click
on BdpConnection1 and select "Connection Editor"
Delphi will open the BdpConnection1 Connection Editor:
you can click "test" to check the connection
Dragging and droping a connection from the Data Explorer is the easiest way
to create a connection to our Interbase Database.
Some developer prefer to use pure code, mainly because it is easier to see
what steps are involved. So lets do a connection by code.
If this had not been the case, we would have been forced to type
the SqlConnection string by hand. This string is made of many parts, including
a binary assembly signature. Using Google, we can find sites with those
connection strings.
In our case, we will write code which does a manual initialisation, using the
connection string from our previous examples:
add a Button to the Form, rename it "connect_", create its Click event, and type the
connection code:
const k_order_entry_database= 'IB_ORDER_ENTRY.GDB';
k_connection_string=
k_assembly
+ ';'+ k_database
+ ';'+ k_user
+ ';'+ k_password
;
with g_c_bdp_connection do
begin
// -- add the StateChange event
Include(StateChange, Self.BdpConnection2_StateChange);
Open();
end; // with g_c_bdp_connection
end; // connect__Click
the INVOICE table contains an ID, the NAME of the customer and a
DATE
each invoice contains several items, each item being defined with an
ID, a parent INVOICE NUMBER, the QUANTITY, the
DESCRIPTION and the PRICE of the item
The standard CREATE TABLE request which will create our INVOICE table
is the following:
the "i_" prefix that we added to all Invoice field name is a personal
convention
the requests which modify in some way the data on the SQL
Server: CREATE TABLE, DROP TABLE, ADD INDEX,
INSERT rows, ALTER TABLEparameters and so on. We simply
send the text of the request to the Server, and do not expect any data in
return (or possibly a success / error notification code)
the request to retrieve some data (usually full rows, but also aggregates
like COUNT, AVERAGE, SUM etc). For those requests,
the Client must first allocate a reception buffer.
Because in the first case the Client only receives a code (usually an Integer)
and in the second the Client must prepare a complete buffering apparatus, the
methods used but all database components that we know use 2 different
methods. In the case of ADO .NEt the methods are ExecuteNonQuery()
and ExecuteReader().
Here in red are the parts of the ADO .Net architecture involved:
And in detail:
l_result:= l_c_command.ExecuteNonQuery();
BdpConnection1.Close();
end; // create_invoice_Click
We can use the BdpConnection to get back the Schema of any table. This
involves an iDataReader INTERFACE which will be explained later. So we
will not explain this code now, but it is included in the downloadable .ZIP file
To remove a Table from the database, we use the following SQL request:
DROP TABLE invoice
The steps to send this request are the same as the code used for creating a
table:
drop a Button, rename it "drop_invoice_", create its Click event, and add the code
allowing us to drop the invoice table:
const k_drop_invoice_table= 'DROP TABLE invoice';
We also added the code to create, display the schema, and drop the ITEM
table. This second table will be used to display a Master Detail relation. The
code is in the .ZIP.
4.4.8 - The complete project
To add data rows to our INVOICE table, we use the following SQL request:
Using the same technique as the one used to create the Table, we could create
a procedure for each row insertion.
for each row to insert, we will use a line like the following:
execute_non_query(do_execute_.Checked, l_request);
end; // fill_the_invoice
Therefore:
create a new Windows Forms project and rename it "p_22_ib_fill_table"
begin // fill_invoice__Click
BdpConnection1.Open();
BdpConnection1.Close();
end; // fill_invoice__Click
We can display (and edit) the rows using the Data Explorer:
select the Data Explorer tab, the "Interbase" line, and the
ib_order_entry_connection
right click on "ib_order_entry_connection" and select "Refresh"
click on "ib_order_entry_connection"
In order to carry out several tests, we added a request allowing to empty the
Table.
Removing rows is performed with the DELETE request. This request very
often includes a WHERE which specifies which rows should be deleted. If we
forget the WHERE, all the row will be scraped ! Therefore, to delete the row
with the ID 105, we could write:
DELETE
FROM invoice
WHERE i_id= 105
In our case we want to remove all the rows. So we will use
a DELETE without any WHERE. Here is the code:
add a Button, rename it "delete_", create its Click event and write the deletion
code. This code will call the execute_non_query procedure presented before:
const k_delete_all_invoice= 'DELETE FROM invoice';
execute_non_query(do_execute_.Checked, k_delete_all_invoice);
BdpConnection1.Close();
end; // delete_invoice__Click
Note that
UPDATE invoice
SET i_customer= 'Martin'
WHERE i_customer= 'Smith'
The execute_non_query procedure was explained before and was duplicated in the
present code.
compile, execute, click "update_invoice_"
here is a snapshot of our project:
4.5.3 - Parameterized Queries
When we send a complex requests (that was not the case above),
the Server tries to optimize the order of the computations. This optimization,
for requests implying many Tables, can take hours. It is then recommended to
proceed in 2 steps:
send a request containing the structure, but not all the literal values. The
request contains placeholders for the future literal values. Since we
only want the Server to compute the order of the computations (but not
the final result), the request is sent using a special Prepare() call:
the Server then computes the best way to perform the computations:
when the Client later wishes to retrieve a result, it sends the litteral
values of the parameters to the Server
the request is now complete, and the Server computes the result and
sends it back to the Client
Each SQL Engine and each access component set use a different syntax for
specifying the parameters of parametrized queries:
Interbase uses ?, and the position is used to match the placeholders with
the BdpParameters array.
So:
UPDATE invoice
SET i_customer= ?
WHERE i_id= ?
o we call BdpCommand.ExecuteNonQuery()
Note that:
drop two TextBox on the Form where the User will place the ID and the
CUSTOMER name
drop another Button on the Form, rename it "execute_", create its Click event and
write the code which fills in the parameters and executes the query:
procedure TWinForm.execute__Click(sender: System.Object;
e: System.EventArgs);
var l_c_bdp_parameter: BdpParameter;
begin
// -- initialize the parmeters
l_c_bdp_parameter:= g_c_bdp_command.Parameters.Add('i_customer',
BdpType.String);
l_c_bdp_parameter.Size:= 7;
l_c_bdp_parameter.Value:= customer_text_box_.Text;
l_c_bdp_parameter:= g_c_bdp_command.Parameters.Add('i_id',
BdpType.Int32);
l_c_bdp_parameter.Value:= Convert.ToInt32(id_text_box_.Text);
g_c_bdp_command.ExecuteNonQuery();
end; // execute__Click
execute, run, and
click "prepare_"
input an ID and a NAME
click "execute_"
We can also use the BdpCommand from the Palette. This will ease the writing
of the request as well as the parameter creation:
select the "Borland Data Provider" tab from the Palette, and click
the BdpCommand component:
Drop the component on the Form
select its CommandText property, and click the ellipsis "..."
the sql request editor is displayed
remove all parameters but I_ID and I_CUSTOMER, and move I_CUSTOMER at
the first position
drop another Button on the Form, rename it "prepare_2_", create its Click event
and write the code which prepares the request:
procedure TWinForm.prepare_2__Click(sender: System.Object;
e: System.EventArgs);
begin
BdpConnection1.Open();
BdpCommand1.ParameterCount:= 2;
BdpCommand1.Prepare();
end; // prepare_2__Click
drop another Button on the Form, rename it "execute_2_", create its Click event
and write the code which fills in the parameters and executes the query:
procedure TWinForm.execute_2__Click(sender: System.Object;
e: System.EventArgs);
begin
BdpCommand1.Parameters[0].Value:= 'william';
BdpCommand1.Parameters[1].Value:= 102;
BdpCommand1.ExecuteNonQuery();
end; // execute_2__Click
click "prepare_2"
click "execute_2"
4.6.1 - SqlDataReader
an BdpConnection
an BdpCommand
an iDataReader object which allows us to read data rows
possibly some display controls
Here is the overall architecture, with in red the DataReader and a TextBox:
the SqlDataReader fetches those rows, and the Client carries out some
computations on the rows. In our case, the rows are displayed in
a TextBox:
Here are the steps:
create a new Windows Forms Application, rename it
"p_31_ib_display_data_reader"
drop an BdpConnection component on the Form and initialize the connection
string to be able to connect to our IB_ORDER_ENTRY database, as explained
before. Check the connection
drop a Button on the Form, rename it "display_invoice_", create its Clic event and
type the code which calls ExecuteReader():
const k_select_invoice= 'SELECT * FROM invoice';
BdpConnection1.Open();
l_c_reader:= l_c_bdp_command.ExecuteReader();
l_row_index:= 0;
while l_c_reader.Read() do
begin
l_display:= '';
for l_column_index:= 0 to l_c_reader.FieldCount- 1 do
l_display:= l_display+ ' '+ l_c_reader.GetValue(l_column_index).ToStrin
g;
display(l_row_index.ToString+ ':'+ l_display);
Inc(l_row_index);
end; // while
BdpConnection1.Close();
end; // display_invoice_click
In the code:
l_c_data_adapter:= BdpDataAdapter.Create;
l_c_data_adapter.SelectCommand:= l_c_bdp_command;
l_c_data_set:= DataSet.Create('my_invoices');
BdpConnection1.Open();
l_c_data_adapter.Fill(l_c_data_set);
BdpConnection1.Close();
l_c_data_table_invoice_ref:= l_c_data_set.Tables[0];
all the objects are created with local variables, which makes it possible
to better understand which components are involved and how they
interact
the connection needs to be open only for the loading of the DataSet.
Once the data has been transfered in memory, the connection can be
closed
it is FILL which is the key instruction. This instruction is called at
runtime. If we use a BdpDataAdapter, this can also be done by
toggling BpdDataAdapter.Active to True at design time (but this is not
possible with the SqlDataAadapter, or the FbDataAdapter)
with dbExpress, the dataflow "direction" is uniform:
my_c_sql_command.CommandText:=
'SELECT * FROM invoice ; SELECT * FROM cities';
l_c_data_set:= DataSet.Create('my_invoices');
BdpConnection1.Open();
l_c_bdp_dataadapter.Fill(l_c_data_set);
BdpConnection1.Close();
DataGrid1.DataSource:= l_c_data_set;
end; // dataset__Click
and when we click on "+":
and on "table":
Note
o the title "my_invoice" was provided during the creation of
the DataSet
o and the navigation icon with a left arrow at the top right corner
of the DataGrid enables to fold the DataTable back.
This tree structure with only one TABLE is of little interest here, and
this is why we connected the DataGrid directly to the
single DataSet.Tables[0]
Instead of creating local components (for the command, the data adapter, the
dataset), me can use components from the Tools Palette:
create a new Windows Forms application, and rename it
"p_33_ib_display_datagrid"
drop an BdpConnection component on the Form and initialize the connection
string to be able to connect to our IB_ORDER_ENTRY database, as explained
before. Check the connection
from the "Data Components" tab of the Tools Palette select the BdpDataAdapter:
We will display the INVOICEs and the ITEMs in two separate DataGrids:
to fill the DataSet1 with the ITEM data at design time, select BdpAdapter2,
and toggle the Active property to True
from the Tools Palette, select a DataGrid and drop it on the Form.
to add a Relation linking our two Tables, select DataSet1, Relations and click the
ellipsis "..."
the Relation Collection Editor is displayed
add the new relation by clicking "Add"
select DataGrid2
select its DataSource property and select DataSet1
select its DataMember property and select INVOICE.Relation1
This is the part of our global architecture diagram that we will focus on:
a collection of DataTable
a collection of DataRelation
a collection of DataView
g_c_invoice_data_table:= DataTable.Create();
g_c_invoice_data_table.Columns.Add('i_id', TypeOf(Integer));
l_c_data_column:= DataColumn.Create('i_customer',
TypeOf(System.String));
l_c_data_column.MaxLength:= 30;
l_c_data_column.AllowDbNull:= False;
g_c_invoice_data_table.Columns.Add(l_c_data_column);
Include(g_c_invoice_data_table.RowChanged, row_changed);
Include(g_c_invoice_data_table.ColumnChanged, column_changed);
end; // create_data_table__Click
But now we will add rows, modify values etc. and this can be traced in several
ways.
First we can connect some events of the DataTable. The carefull reader will
have detected the two Include() lines at the end of our creation procedure.
We did add the RowChanged and ColumnChanged events. To get a list of all
possible events and their signatures, we used the .Net Help:
we pushed F1
Reference
Class Library
Reference
Class Library
System.Data
DataTable Class
If I had only one piece of advice for the newbie .Net developer, it would be to
become familiar with the .Net Help. And this is not very difficult: the main
parts are
Note that:
to avoid writing the events with their strange parameters, we could have
dropped a DataSet on the Form, created the events, removed the
bogus DataSet and graftet the orphan events to our in
memory DataTable
l_c_data_row['i_id']:= p_id;
l_c_data_row['i_customer']:= p_customer;
g_c_invoice_data_table.Rows.Add(l_c_data_row);
end; // add_row
begin // insert__Click
add_row('111', 'cadillac');
add_row('222', 'gm');
add_row('333', 'ford');
end; // insert__Click
drop another Button, "display_", and write the code which displays
the DataTable content
execute, run, and click "insert_" and then "display_"
here is the snapshot of our project:
Note that:
if p_c_data_row.HasVersion(DataRowVersion.Proposed)
then l_proposed_value:= p_c_data_row[l_column_index,
DataRowVersion.Proposed].ToString()
else l_proposed_value:= '';
l_value:= l_current_value;
if (l_current_value<> l_proposed_value)
then l_value:= l_value+ ' [=> '+ l_proposed_value+ ']';
Result:= Result+ ' '+ l_value;
end; // vor l_column_index
end; // f_display_data_row_state_version
while l_c_row_enumerator.MoveNext() do
begin
l_c_data_row:= l_c_row_enumerator.Current as DataRow;
display(f_display_data_row_state_version(l_c_data_row, 2));
end; // while l_c_row_enumerator
end; // display_all__Click
To display the values of the state enumeration, we used the Format function.
Here is an example:
So:
when we call BeginEdit, all rows modified before EndEdit are recorded
with a modification marker, and the DataTable contains both the old
value (called Current) and new value (called Proposed)
o we first test
my_data_row.HasVersion(DataRowVersion.Proposed)
my_enumerated_type.my_enumerated_value
The complete project which creates, inserts, updates and deletes is in the
downloadable 41_ib_create_in_memory.zip file
4.7.7 - Views
my_c_data_view:= my_c_data_table.DefaultView;
procedure create_column_definitions;
var l_c_data_column: DataColumn;
begin
l_c_invoice_data_table.Columns.Add('i_id', TypeOf(Integer));
l_c_invoice_data_table.Columns.Add('i_amount', TypeOf(Double));
end; // create_column_definition
procedure insert_row_values;
l_c_data_row['i_id']:= p_id;
l_c_data_row['i_customer']:= p_customer;
l_c_data_row['i_amount']:= p_amount;
l_c_invoice_data_table.Rows.Add(l_c_data_row);
end; // add_invoice
begin // insert_row_values
add_invoice(201, 'apple', 1234.51);
add_invoice(202, 'exxon', 625.51);
add_invoice(203, 'dow', 334.51);
add_invoice(204, 'ibm', 134.51);
end; // insert_row_values
begin // create_data_set__Click
g_c_data_set:= DataSet.Create('business');
l_c_invoice_data_table:= DataTable.Create();
l_c_invoice_data_table:= g_c_data_set.Tables.Add('invoice');
create_column_definitions;
insert_row_values;
l_c_data_view:= DataView.Create(l_c_invoice_data_table);
display_data_view(l_c_data_view, 3);
end; // create_data_set__Click
drop a Button, rename it "sort_", create its Click event, and add code to sort
the DataTable
procedure TWinForm.sort__Click(sender: System.Object;
e: System.EventArgs);
var l_c_sorted_data_view: DataView;
begin
l_c_sorted_data_view:= g_c_data_set.Tables[0].DefaultView;
l_c_sorted_data_view.Sort:= 'i_customer ASC';
display_data_view(l_c_sorted_data_view, 3);
end; // sort__Click
// -- MUST sort
l_c_sorted_data_view:= g_c_data_set.Tables[0].DefaultView;
l_c_sorted_data_view.Sort:= 'i_customer ASC';
display_data_view(l_c_sorted_data_view, 3);
l_find_index:= l_c_sorted_data_view.Find(l_string_to_find);
display('found at '+ l_find_index.ToString);
end; // find__Click
drop a Button on the Form, name it "filter_", create its Click event and add an
expression which will filter the DefaultView:
procedure TWinForm.filter__Click(sender: System.Object;
e: System.EventArgs);
var l_c_filtered_data_view: DataView;
begin
l_c_filtered_data_view:= g_c_data_set.Tables[0].DefaultView;
l_c_filtered_data_view.RowFilter:= ' i_customer> ''g'' ';
display_data_view(l_c_filtered_data_view, 3);
end; // filter__Click
we performed the filtering after all other examples. In our case, the
filter only left one row, and sorting would not have been very
interesting.
there are all kinds of other possibilities: launching SQL requests on the
in memory DataSet, compute aggregates etc, but we will not present
those in this tutorial.
my_data_grid.DataSource:= my_data_table.DefaultDataView;
This works well when transfering data from the DataSet to a visual control.
But in order to transfer modifications from a visual control back to
the DataSet, we must use DataBindings:
These DataBindings are rather delicate to handle, and I found
on Google several postings which were much more severe about them.
drop a Button, rename it "create_data_table_", create its Click event, and add some
code which will create and fill the DataSet:
procedure TWinForm.create_data_table__Click(sender: System.Object;
e: System.EventArgs);
var l_c_invoice_data_table: DataTable;
procedure create_column_definitions;
var l_c_data_column: DataColumn;
begin
l_c_invoice_data_table.Columns.Add('i_id', TypeOf(Integer));
procedure insert_row_values;
l_c_data_row['i_id']:= p_id;
l_c_data_row['i_customer']:= p_customer;
l_c_data_row['i_amount']:= p_amount;
l_c_invoice_data_table.Rows.Add(l_c_data_row);
end; // add_invoice
begin // insert_row_values
add_invoice(201, 'macy', 1234.51);
add_invoice(202, 'exxon', 625.51);
add_invoice(203, 'dow', 334.51);
add_invoice(204, 'ibm', 134.51);
end; // insert_row_values
begin // create_data_set__Click
m_c_data_set:= DataSet.Create('business');
l_c_invoice_data_table:= DataTable.Create();
l_c_invoice_data_table:= m_c_data_set.Tables.Add('invoice');
create_column_definitions;
insert_row_values;
end; // create_data_set__Click
customer_text_box_.DataBindings.Add('Text',
m_c_data_set, 'invoice.i_customer');
g_c_currency_manager:=
BindingContext[m_c_data_set.Tables[0].DefaultView] as CurrencyMana
ger;
Include(g_c_currency_manager.CurrentChanged, current_changed);
Include(g_c_currency_manager.PositionChanged, position_changed);
end; // bind_data_table__Click
drop a Button on the Form, rename it "next_", create its Click event which will
modify the position of the CurrencyManager:
procedure TWinForm.next__Click(sender: System.Object;
e: System.EventArgs);
begin
BindingContext[m_c_data_set, 'invoice'].Position:=
BindingContext[m_c_data_set, 'invoice'].Position+ 1;
end; // next__Click
We will now bind visual controls to an ARRAY. This will work because in
.Net, the dynamic ARRAYs do implement the iList INTERFACE.
type c_invoice=
class
Public
m_id: Integer;
m_customer: System.String;
m_amount: Double;
g_c_invoice_array[l_invoice_index]:= l_c_invoice;
Inc(l_invoice_index);
end; // add_invoice
begin // create_array__Click
SetLength(g_c_invoice_array, 4);
l_invoice_index:= 0;
add_invoice(201, 'macy', 1234.51);
add_invoice(202, 'exxon', 625.51);
add_invoice(203, 'dow', 334.51);
add_invoice(204, 'ibm', 134.51);
end; // create_array__Click
Some notes:
To let the user modify Server data, we proceed into two stages
Let us first modify the data by hand (not by typing values in visual controls).
Here is the modifying code:
create a new Windows Forms application, and rename it
"p_51_ib_update_via_code"
drop an BdpConnection component on the Form and initialize the connection
string to be able to connect to our IB_ORDER_ENTRY database, as explained
before. Check the connection
drop a Button, rename it "change_invoice_", create its Click event, and change
some values of the in memory DataTable:
var g_c_bdp_data_adapter: BdpDataAdapter;
g_c_data_set: DataSet;
// -- insert a row
l_c_data_row:= l_c_invoice_data_table.NewRow();
l_c_data_row['i_id']:= Convert.ToInt32(id_textbox_.Text)+ 5;
l_c_data_row['i_customer']:= customer_textbox_.Text;
l_c_invoice_data_table.Rows.Add(l_c_data_row);
// -- modify a row
display('modify_row');
l_c_data_row:= l_c_invoice_data_table.Rows[3];
l_c_data_row['i_customer']:= customer_textbox_.Text;
display('delete_row');
l_c_invoice_data_table.Rows[Convert.ToInt32(delete_id_textbox_.Text)].De
lete;
end; // change_invoice__Click
we examine each line of the DataTable, and check its State value: if the
row was somehow changed, we write and sent the relevant SQL request
for INSERT, it is very simple: we simply use the new values to fill
the VALUES clause of the INSERT INTO request
for UPDATE we stumble, as always, on the problem of concurrent
accesses:
o in the WHERE clause we must specify which rows we want to
modify
o the in-memory DataSet still contains the original values that we
fetched at the time of the SELECT call, and we therefore know
the original key (ID) of the row. So we can ask to modify the
row having this previous key
o we can also, if the application requires it, check that other values
did not change. For a plane reservation, we certainly also would
check that the available seats did not change between
the SELECT call and the reservation request (the customer could
ask whether there are some available seats, think for some time,
and later, much later, finally decide to book a flight). In this case,
we could include in the WHERE clause some code checking that
the actual Serverdata did not change, by comparing the available
seats to those available when we first sent the SELECT
while l_c_row_enumerator.MoveNext() do
begin
l_c_row:= l_c_row_enumerator.Current as DataRow;
case l_c_row.RowState of
DataRowState.Added : do_insert_row(l_c_row);
DataRowState.Modified : do_update_row(l_c_row);
end; // case
end; // while l_c_row_enumerator
BdpConnection1.Close();
end; // update_manual_Bdp__Click
l_c_bdp_command.ExecuteNonQuery;
end; // do_update_row
We can change the Table values using visual controls. Here the project:
create a new Windows Forms application, and rename it
"p_52_ib_update_via_datagrid"
drop an BdpConnection component on the Form and initialize the connection
string to be able to connect to our IB_ORDER_ENTRY database, as explained
before. Check the connection
set up the BdpDataAdapter / DataSet / DataGrid for INVOICE:
drop a Button, create its Click event, and add the code which
calls BdpDataAdapter.Update()
execute, run, modify some values, then click "bdpdataadapter_update_"
here is the snapshot of our project:
the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM),
and any other auxiliary form
any .TXT for parameters, samples, test data
all units (.PAS) for units
Those .ZIP
are self-contained: you will not need any other product (unless
expressly mentioned).
for Delphi 6 projects, can be used from any folder (the pathes are
RELATIVE)
will not modify your PC in any way beyond the path where you placed
the .ZIP (no registry changes, no path creation etc).
The Pascal code uses the Alsacian notation, which prefixes identifier by
program
area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse
etc. This notation is presented in the Alsacian Notation paper.
As usual:
Name :
E-mail :
Comments * :
Send my Comments
and if you liked this article, talk about this site to your fellow
developpers, add a link to your links page ou mention our articles in
your blog or newsgroup posts when relevant. That's the way we
operate: the more traffic and Google references we get, the more
articles we will write.
6 - References
We published this paper in september 2006, one week after the release of
Turbo Delphi. Hopefully this will help promote this new version of Delphi. I
also contacted John Kaster from Borland on September the 18th, who
answered "Thanks much. I'll ask the team if they want to provide a link to it.".
So maybe there will be a link to this tutorial
on Delphi Community or Interbase Community some day. Craig
Stuntz (TeamB) on the other hand kindly posted a link to this paper in his
popularDelphi / Interbase blog (Dec 11 2006). Many thanks to Craig.
7 - The author
Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in
1979, he then became involved with Object Oriented Programming, Delphi,
Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom
software development (new projects, maintenance, audits, BDE migration,
Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming
with full downloadable source code, and the description and calendar of
forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML,
Design Patterns, Unit Testing training sessions.