|
Turbo Delphi Ado Net BDP - John COLIBRI. |
- résumé : Programmation d'applications ADO Net d'une base SQL Server, en utilisant Turbo Delphi et le BDP
- mots clé : SQL Server - ADO.Net - BDP - Borland Data Provider - Delphi - Windows Forms - SQL Serveur
- logiciel utilisé : Windows XP Home - .Net SDK 1.1.432 - Turbo Delphi For Net
- matériel utilisé : Pentium 2.800Mhz, 512 M de mémoire, 250 Giga disque dur
- champ d'application : développeur base de données Windows .Net
- niveau : développeur Delphi ou autre langage .Net
- plan :
1 - Introduction Nous allons présenter comment développer des applications de bases de données utilisant Firebird en mode ADO.Net avec Delphi. Nous allons présenter Nous ne pouvons pas tout présenter ici. Ne sont pas présentés dans ce document - les procédures cataloguées, les triggers, les séquences
- les transactions et la gestion de la concurrence
- le détail des contrôles visuels (TreeView, ListView etc)
Rappelons aussi que nous organisons tous les mois des formations, et en
particulier pour ceux intéressés par les bases de données et .Net:
2 - Principe 2.1 - Architecture Client Serveur Les applications de bases de données, que ce soit Sql Server, Oracle,
Interbase, Firebird, MySql etc, fonctionnent essentiellement en mode Client Serveur. Dans ce modèle - un logiciel appelé le Serveur gère les transferts des données entre les
applicatifs et le stockage disque
- les applicatifs Client envoient des requêtes au Serveur qui leur fournit les données demandées.
En supposant, ce qui est fréquemment le cas, que le Server et les Clients
soient sur des PC différents, la communication entre les deux transite par un réseau (TCP/IP ou autre). Schématiquement nous avons donc - le Serveur qui comporte
- le stockage disque
- le logiciel Serveur (le Serveur SQL Server pour nous)
- les couches réseau
- un ou plusieurs Clients contiennent
- les couches réseau (Tcp/Ip ici)
- le client (le Client SQL Server dans notre cas)
- les composants de bases de données (ADO.Net)
- un ou plusieurs logiciels applicatifs (une comptabilité, un portail Web etc)
- le Serveur se met à l'écoute, puis un Client envoie une requête :
- le Serveur analyse la requête, calcule les données qui constituent la réponse et renvoie le tout en un ou plusieurs paquets au Client
- et naturellement il y a en général plusieurs Clients qui utilisent le Serveur:
Pour que cette mécanique fonctionne, nous devons installer:
- le Serveur SQL Server
- le Client SQL Server
- les composants d'accès aux données (.Net Framework et ADO.Net)
- l'outil de développement (Delphi 2005)
Nous avons déjà présenté comment installer SQL Server. Pour les experts - vous cherchez le logiciel via Google
- vous le téléchargez
- vous lancez l'installation
Si cela ne fonctionne pas, voyez l'article cité qui explique tout par le menu détail, avec les tests à toutes les étapes.
Pour le .Net Framework, il est installé lors de l'installation de Delphi 2005, Delphi 2006 ou Turbo Delphi for .Net. Sinon Google regorge de tutoriaux qui vous diront comment faire.
Le .Net Framework installé avec Delphi vous permet d'utiliser les bases Interbase, Sql Server et Oracle
2.2 - Organisation des répertoires Nous allons utiliser une base de données que nous placerons en
c:\programs\fr\bdd\turbo_delphi_bdp\_data
3 - Architecture ADO .Net Nous allons présenter quelques applications qui vont - créer et remplir des tables
- afficher le résultat dans des composants visuels (DataGrid)
- mettre à jour les données depuis les composants visuels.
Nous utiliserons les différents composants ADO.Net. Mais avant de plonger dans
le code, présentons l'architecture de ces composants.
3.1 - Structure Globale ADO.Net est composée de 4 catégories de composants: - des composants qui dialoguent directement avec la base de données pour
assurer la connexion et exécuter les requêtes SQL. Ces composants forment le DataProvider
et: - BdpConnection permet d'établir la connexion avec le Serveur
- BdpCommand est utilisé pour lancer les requêtes (SELECT ou CREATE, INSERT etc)
- BdpDataReader est utilisé pour récupérer et tamponner les SELECT. Ces données peuvent ensuite être manipulées par programme (calculs, affichage
dans un TextBox)
- un composant chargé de transférer les données entre le DataProvider et les composants qui stockent les données en mémoire: c'est le BdpDataAdapter. Il contient essentiellement
- quatre BdpCommand, permettant chaque type de commande SQL possible: SELECT, INSERT, DELETE, UPDATE
- un composant TableMappings qui permet de donner des noms "lisibles" aux
noms attribués par défaut par ADO.Net (Table1, Table2 etc peuvent être remplacés par Facture ou Livraisons)
soit:
- des composants qui stockent en mémoire les données fournies:
- par le BdpDataAdapter
- par une lecture d'un fichier disque (.XML par exemple)
- par des instructions de code
Ces composants sont appelés DataSet, et comportent - des tables DataSet (provenant de table du Serveur)
- des contraintes (NOT NULL etc)
- des relations (clés étrangères pour établir des relations maître détail)
- des vues, permettant le filtrage, la projection, le tri, les agrégats, les recherches etc
Soit:
- finalement nous souhaitons afficher et manipuler les données dans des contrôles visuels. Nous utilisons pour cela
- des contrôles, tels que des TextBox, ListBox ou DataGrid
- des composants DataBindings qui synchronisent les modifications effectuées dans les contrôles avec les composants qui stockent les données
Voici donc le tableau final: - de plus, comme le DataSet contient des tables en mémoire, il peut
- être créé et chargé à partir de code
- sauvegarder et recharger des données depuis un fichier (.XML ou autre)
- et les contrôles visuels peuvent être alimentés par des données autres que des DataSets, par exemple des tableaux:
- le BdpDataAdapter que nous avons présenté ci dessus permet
- le chargement des données depuis le Serveur dans le DataSet: c'est l'instruction BdpDataAdapter.Fill()
- la sauvegarde des modifications effectuées dans les contrôles visuels: c'est l'instruction BdpDataAdapter.Update()
Que vient faire le BdpDataProvider dans cette affaire ? Eh bien le .Net Framework et Delphi sont livrés avec des DataProvider standards tels que
- un provider pour Sql Server (SqlConnection, SqlCommand, SqlDataAdatper etc)
- un provider pour OleDb. OleDb est la mécanique qui permettait via COM
l'accès à "toute" source de données: serveur SQL naturellement, mais aussi mail, Excel etc. Donc ADO.Net permet de récupérer ce type de connection via un DataProvider
- un Borland Data Provider qui est une généralisation du DataProvider, et que nous avons déjà présenté
Pour accéder aux données SQL Server nous pouvons donc utiliser plusieurs chemins:
Dans cet article, nous nous intéresserons à la programmation directe ADO.Net, en utilisant le BDPDataProvider.
4 - La programmation ADO.Net
4.1 - Les paramètres de notre SQL Server Les paramètres que nous avons fournis lors de l'installation de SQL Server sont les suivants:
- HostName: pol_1400_5\my_instance
- User: sa
- Password: my_pass
Vous remplacerez ces valeurs par celles de votre moteur SQL Server
4.2 - La création de la base
Pour créer une base SQL Serveur, il faut envoyer la requête CREATE DATABASE avec des paramètres propre à ce moteur. Nous utiliserons la requête suivante:
CREATE DATABASE order_entry
ON PRIMARY (
NAME = 'the_order_entry_data',
FILENAME= 'c:\programs\fr\bdd\sql_server\turbo_delphi_bdp\_data\the_order_entry_data.mdf' )
LOG ON (
NAME = 'the_order_entry_log',
FILENAME ='c:\programs\fr\bdd\sql_server\turbo_delphi_bdp\_data\the_order_entry_log.ldf' )
| Cette requête est utilisée de la façon suivante: - nous nous connectons à une base système appelée MASTER. Cette connection est effectuée en utilisant un BdpConnection
- la requête elle-même est placée dans un BdpCommand qui est envoyé vers le Serveur en appelant ExecuteNonQuery
Voici l'application: |
lancez Delphi | | Delphi présente l'IDE  |
Nous allons commencer par vérifier que nous arrivons à nous connecter à MASTER. Pour cela, nous allons utiliser l'Explorateur de bases de données, situé dans le NoteBook en haut à droite, troisième onglet:
| dans le NoteBook en haut à droite, sélectionnez l'onglet "Data Explorer" |
| sélectionnez MSSQL, effectuez un click droit, et sélectionnez "Add New Connection" | |
Delphi demande quel nom vous souhaitez donner à cette connection dans le tTreeView: |
| tapez un nom de connection, par exemple master_connection et cliquez "Ok" |
| une nouvelle connection est ajoutée | |
faites un clic droit souris, et sélectionnez "modify connection" | | un Editeur de connection est affiché:
| | tapez les valeurs pour DataBase (MASTER), HostName
(pol_1400_5\my_instance), l'utilisateur (sa) et le mot de passe (my_pass) Cliquez "Ok" Réouvrez l'Editeur (clic droit, "modify connection" et cliquez "Test" |
| la connexion est confirmée:  |
Voici comment créer la base:
| créez une nouvelle application Windows Forms : "file | New | Delphi Windows Forms", et renommez-la "p_01_bdp_create_database"
| | dans le Data Explorer, sélectionnez "MSSQL | master_connection" et
tirez-le SUR LA FORME | | Delphi affiche BdpConnection1 dans la zone des composants non-visuels, SOUS la Forme:
| |
posez un tButton sur la Forme, renommez-le "create_database_", créez son événement OnClick et écrivez le code qui va créer la base:
const k_database_name= 'order_entry';
k_path= 'c:\programs\fr\bdd\sql_server\turbo_delphi_bdp\_data\';
k_create_database= ' CREATE DATABASE order_entry'
+ ' ON PRIMARY '
+ ' ('
+ ' NAME = ''the_order_entry_data'' '
+ ' , FILENAME = '''+ k_path
+ 'the_'+ k_database_name+ '_data.mdf'''
+ ' ) '
+ ' LOG ON '
+ ' ('
+' NAME = ''the_order_entry_log'' '
+ ' , FILENAME = '''+ k_path
+ 'the_'+ k_database_name+ '_log.ldf'' '
+ ' ) '
;
procedure TWinForm.create_database___Click(sender: System.Object;
e: System.EventArgs);
var l_c_bdp_command: BdpCommand; begin
BdpConnection1.Open;
l_c_bdp_command:= BdpCommand.Create(k_create_database, BdpConnection1);
l_c_bdp_command.ExecuteNonQuery(); BdpConnection1.Close
end; // create_database___Click | | |
compilez, exécutez et cliquez "create_database_" | | voici le répertoire contenant notre nouvelle base:
 |
Vous pouvez télécharger le .ZIP 01_bdp_create_database.zip qui contient en
outre des boutons pour afficher toutes les tables connues de SQL Server (MASTER, ORDER_ENTRY etc), tester la connexion, effacer la base (DROP DATABASE .) Comme ces options utilisent des techniques présentées
ci-dessous, nous ne rentrerons pas dans le détail du code
4.3 - La connection 4.3.1 - Les différentes méthodes Nous allons commencer par établir la connexion entre notre application Delphi
et la base ORDER ENTRY située sur le Serveur. Nous examinerons plusieurs techniques: - le Data Explorer
- du code Delphi
- le composant BDP
4.3.2 - Connection dans le Data Explorer Nous allons ajouter une entrée dans le Data Explorer pour nous connecter à notre nouvelle base (c'est la même manipulation que pour ajouter MASTER):
| dans le NoteBook en haut à droite, sélectionnez l'onglet "Data Explorer" |
| sélectionnez MSSQL, effectuez un click droit, et sélectionnez "Add New Connection" | |
Delphi demande quel nom vous souhaitez donner à cette connection dans le tTreeView. Tapez un nom de connection, par exemple order_entry_connection et cliquez "Ok" |
| une nouvelle connection est ajoutée | |
faites un clic droit souris, et sélectionnez "modify connection" | | un Editeur de connection est affiché: |
| tapez les valeurs pour DataBase (ORDER_ENTRY), HostName (pol_1400_5\my_instance), l'utilisateur (sa) et le mot de passe (my_pass)
Cliquez "Ok" Réouvrez l'Editeur (clic droit, "modify connection" et cliquez "Test" |
| la connexion est confirmée |
4.3.3 - Utilisation de BdpConnection Nous souhaitons à présent nous connecter à la base ORDER_ENTRY dans nos propres
projets. Nous utiliserons un composant de connexion, représenté en rouge dans ce schéma d'architecture:
4.3.4 - La connexion par code Nous allons à présent réaliser la connexion par code uniquement. Pour cela, il faut: - créer un composant BdpConnection
- remplir la propriété ConnectionString
Voici le projet:
| créez une nouvelle application Windows Forms : "file | New | Delphi Windows Forms", et renommez-la "p_11_connect_order_entry" |
| posez un Button sur la Forme, renommez-le "connect_", créez son événement OnClick et écrivez le code qui crée un BdpConnection, initialise
ConnectionString et ouvre la connexion:
const k_database_name= 'order_entry';
k_user_sa= 'user id=sa';
k_assembly= 'assembly=Borland.Data.Mssql, Version=2.5.0.0, Culture=neutral, '
+ 'PublicKeyToken=91d62ebb5b0d1b1b';
k_password_me= 'Pwd = my_pass';
k_sspi_security= 'integrated security=SSPI';
k_server_name= 'SERVER= "pol_1400_5\my_instance"';
k_database_parameter= 'DATABASE= '+ k_database_name;
k_connection_string= k_user_sa
+ ';'+ k_password_me
+ ';'+ k_sspi_security
+ ';'+ k_server_name
+ ';'+ k_assembly
+ ';'+ k_database_parameter;
procedure TWinForm1.connect__Click(sender: System.Object;
e: System.EventArgs);
var l_c_bdp_connection: BdpConnection; begin
display('> connect'); display(k_connection_string);
try
l_c_bdp_connection:= BdpConnection.Create(k_connection_string);
display('ok_success') except
on e: Exception do
display('*** err '+ e.Message);
end; display('< connect');
end; // connect__Click | | |
compilez, exécutez et cliquez "connect_" | | voici une vue du projet: 
|
4.3.5 - Utilisation du composant BdpConnection Au lieu de créer le composant BdpConnection, nous pouvons le déposer depuis la Palette:
| fermez les onglets de la Palette (click droit et "collapse all", et ouvrez l'onglet "Borland Data Provider" Sélectionnez BdpConnection:
et posez le composant SUR LA FORME | |
Delphi l'affiche dans la zone des composants non-visuels (flèche rouge) |
| pour initialiser les paramètres de BdpConnection1, cliquez le lien "Connection Editor" situé sous l'Inspecteur d'Objet (flèche jaune), ou bien utilisez clic droit souris sur BdpConnection1, "connection editor" |
| l'Editeur de Connection BDP s'affiche:
Par défaut, la première entrée est affichée (Interbase) | | sélectionnez "order_entry_connection", et cliquez "test" pour vérifier la connexion |
4.3.6 - Utilisation du Data Explorer Voici une autre façon encore de créer et initialiser un BdpConnection: |
dans le NoteBook en haut à droite, sélectionnez "Data Explorer | MSSQL | order_entry_connection" et tirez cette connexion sur la Forme | |
Delphi crée un BdpConnection qui est déjà entièrement initialisé (ouvrez l'Editeur de connexion BDP et cliquez "Test" pour vous en assurer) |
4.3.7 - Quelle technique de connexion utiliser ?
Assurément le "tirer-glisser" de la connexion depuis le Data Explorer est la façon la plus efficace. Elle comporte quelques inconvénients: - il faut créer une entrée
- certains développeurs sont réticents à utiliser l'Explorateur
- tous les moteurs n'ont pas encore la possibilité d'utiliser l'Explorateur (Firebird, par exemple)
Les autres techniques présentées peuvent alors être utilisées. Dans le reste de cet article, nous utiliserons l'Explorateur Pour télécharger le source, cliquez p_01_connect.zip
4.4 - Création de table 4.4.1 - CREATE TABLE Nous allons placer dans notre base des tables pour gérer une petite facturation. La base contiendra deux tables - un table de factures, INVOICE
- une table des articles de la facture, ITEM
Voici le schéma de nos tables: Pour créer la table INVOICE, nous envoyons vers SQL Server la requête SQL suivante:
CREATE TABLE invoice
(
i_id INTEGER NOT NULL PRIMARY KEY,
i_customer CHARACTER(7), i_date DATETIME
) | Notez que le préfixe "i_" pour désigner les champs de Invoice n'est pas nécessaire.
Pour envoyer cette requêtes vers le Serveur, nous utiliserons les composants
suivants: - BdpConnection pour la connexion
- BdpCommand pour exécuter la requête SQL de création
Soit schématiquement:
Le fonctionnement des requêtes qui vont modifier les données du Serveur (création de table, effacement de table, ajout de données, création d'index etc) est le suivant:
4.4.2 - La création Par conséquent:
| créez une nouvelle application Delphi et renommez-la "p_21_bdp_create_table" | |
dans le NoteBook en haut à droite, sélectionnez "Data Explorer | MSSQL | order_entry_connection" et tirez cette connexion sur la Forme | |
Delphi place un BdpConnection1 tout initialisé dans la zone des composants non-visuels | |
posez un tButton sur la Forme, renommez-le "create_invoice_", créez son événement OnClick et écrivez le code qui va envoyer la requête CREATE TABLE vers le Serveur:
const k_create_invoice_table=
'CREATE TABLE invoice '+ k_new_line
+ ' ('+ k_new_line
+ ' i_id INTEGER NOT NULL PRIMARY KEY'+ k_new_line
+ ' , i_customer CHARACTER(7)'+ k_new_line
+ ' , i_date DATETIME'+ k_new_line
+ ' )';
procedure TWinForm.create_invoice__Click(sender: System.Object;
e: System.EventArgs);
var l_c_command: BdpCommand;
l_result: Integer; begin
BdpConnection1.Open();
l_c_command:= BdpCommand.Create(k_create_invoice_table, BdpConnection1);
l_result:= l_c_command.ExecuteNonQuery();
display('Result '+ l_result.ToString);
BdpConnection1.Close(); end; // create_invoice_Click |
| | compilez, exécutez et cliquez "create_invoice_" | |
voici une vue du projet:  |
Notez que: - au lieu de créer BdpCommand par code, nous aurions pu utiliser le composant
de la Palette
Nous pouvons visualiser la nouvelle table dans l'Explorateur de données:
4.4.3 - DROP TABLE Nous ajoutons immédiatement l'instruction SQL qui nous permettra de supprimer
la table, pour pouvoir effectuer autant de modifications que nous souhaitons. La requête SQL est:
Nous avons donc ajouté un nouveau bouton pour permettre l'effacement de la table.
Notez que : - nous avons placé toutes nos requêtes dans des constantes Pascal. Ceci nous
permet de trouver tout le SQL à un seul endroit de notre application et permet plus facilement de vérifier sa cohérence ou de le modifier par couper / coller, mise en commentaire etc
- notre application comporte aussi
- un TextBox pour afficher les messages de mise au point (unité u_c_dot_net_display qui se trouve avec chaque .ZIP)
- une CheckBox qui nous permet d'afficher le SQL que nous envisageons
d'envoyer. En effet la constant Delphi comporte des guillemets Delphi, et éventuellement des guillemets SQL, et il arrive d'en oublier. Une vérification préalable permet de gagner du temps
- nous aurions aussi pu ajouter des exceptions pour piéger nos erreurs, mais cela est trivial
- nous avons aussi ajouté des méthodes pour créer et effacer la table des ITEMS
- le projet affiche aussi le schéma des tables créées
Vous pouvez télécharger le projet p_21_bdp_create_table.zip
4.4.4 - Ajout de données Les autres instructions de modifications de TABLE (écriture de données,
modification de valeurs, effacement de ligne) utilisent les mêmes composants ADO.Net que ceux de la création. Pour ajouter un nouveau stage, la requête SQL est la suivante:
INSERT INTO invoice
(i_id, i_customer, i_date)
VALUES (101, 'Smith', '9/21/2006') |
Nous pouvons utiliser la même technique que précédemment, mais cela nous oblige à créer une requête pour chaque ligne. Nous avons choisi d'utiliser une procédure Delphi paramétrée ce qui nous permet d'ajouter une ligne avec une
seule instruction Delphi: - dans la méthode Click, nous appelons des lignes telles que:
fill_the_invoice(100, 'Smith', '9/12/2006');
fill_the_invoice(101, 'DevShop', '9/14/2006');
fill_the_invoice(102, 'EastMfg', '9/14/2006'); | - la procédure fill_the_invoice est:
procedure fill_the_invoice(p_id: Integer; p_customer, p_date: System.String);
var l_values, l_request: System.String;
begin l_values:= p_id.ToString
+ ', '''+ p_customer+ ''''
+ ', '''+ p_date+ '''';
l_request:= 'INSERT INTO invoice '
+ ' (i_id, i_customer, i_date) '+ k_new_line
+ ' VALUES ('+ l_values+ ')';
execute_non_query(do_execute_.Checked, l_request);
end; // fill_the_invoice | - et execute_non_reader est notre procédure générique d'écriture:
procedure TWinForm.execute_non_query(p_do_execute: Boolean;
p_request: System.String);
var l_c_command: BdpCommand;
l_count: Integer; begin
if p_do_execute then begin
l_c_command:= BdpCommand.Create(p_request, BdpConnection1);
Try
l_count:= l_c_command.ExecuteNonQuery();
except
on e: exception do
display_bug_stop(e.Message);
end; end;
end; // execute_non_query |
Par conséquent:
| créez un nouveau projet et nommez-le "p_22_bdp_fill_table" | |
dans l'Explorateur sélectionnez la connexion order_entry et tirez la sur la Forme | |
posez un Button sur la Forme et créez son clic. Tapez les instructions qui remplissent la table en utilisant la procédure Delphi paramétrée présentée ci-dessus:
procedure TWinForm.fill_invoice__Click(sender: System.Object;
e: System.EventArgs);
procedure fill_the_invoice(p_id: Integer; p_customer, p_date: System.String);
var l_values, l_request: System.String;
begin l_values:= p_id.ToString
+ ', '''+ p_customer+ ''''
+ ', '''+ p_date+ '''';
l_request:= 'INSERT INTO invoice '
+ ' (i_id, i_customer, i_date) '+ k_new_line
+ ' VALUES ('+ l_values+ ')';
execute_non_query(do_execute_.Checked, l_request);
end; // fill_the_invoice begin // fill_invoice__Click
BdpConnection1.Open();
fill_the_invoice(100, 'Smith', '9/12/2006');
fill_the_invoice(101, 'DevShop', '9/14/2006');
fill_the_invoice(102, 'EastMfg', '9/14/2006');
BdpConnection1.Close(); end; // fill_invoice__Click |
| | compilez, exécutez et cliquez "fill_invoice_" | |
voici une vue du projet:  |
Notez que - c'est dans execute_non_query centralisée que nous effectuons nos affichages
de contrôle
- la création des valeurs VALUE nécessite quelques précautions:
- les chaînes doivent être entourées de guillemets simples
- la conversion de valeur réelles nécessite de batailler avec le caractère
de décimales (PI aux US se note 3.13, mais3,14 en France et la routine de conversion suppose que nous tapons 3.14 sur un PC avec un XP Français. Nous envoyons donc une valeur Delphi 3.14, mais la conversion la change
en '3,14' que nous nous empressons de transformer en '3.14')
- les dates doivent être au format US (mois/jour/année) et doivent être entre guillemets (sinon SQL effectue un division !)
Tout ce petit travail est réalisé par notre procédure fill_the_training
Nous pouvons aussi afficher les données ajoutées dans l'Explorateur:
4.4.5 - Effacement de la Table
Pour pouvoir effectuer plusieurs essais, nous avons ajouté une requête permettant de vider la table. L'effacement de lignes se fait par la requête DELETE, qui pratiquement
toujours comporte un clause WHERE permettant de spécifier quelles lignes nous souhaitons effacer. Pour effacer la ligne ayant l'identificateur 105, nous utiliserions:
DELETE
FROM invoice WHERE i_id= 105 |
Comme dans notre cas nous souhaitons purger TOUTES les lignes, nous supprimons simplement la clause WHERE (effacement inconditionnel). Donc |
ajoutez un Button, et placez-y l'instruction d'effacement qui effacera toutes les lignes de la TABLE présentée ci-dessus, puis compilez et exécutez:
const k_delete_all_invoice= 'DELETE FROM invoice';
procedure TWinForm.delete_invoice__Click(sender: System.Object;
e: System.EventArgs); begin
BdpConnection1.Open();
execute_non_query(do_execute_.Checked, k_delete_all_invoice);
BdpConnection1.Close(); end; // delete_invoice__Click |
|
Notre projet permet: - l'ajoute, et l'effacement de la table ITEM
- l'affichage du contenu des table dans une TextBox, mais ceci va être présenté plus bas.
Vous trouverez ce projet dans p_22_bdp_fill_table.zip
4.4.6 - Modifier une donnée Pour modifier la valeur de données, nous utilisons UPDATE:
UPDATE invoice
SET i_customer= 'Martin'
WHERE i_customer= 'Smith' |
Nous avons placé le code dans un nouveau projet: | créez un nouveau projet et nommez-le "p_23_update_table" |
| dans l'Explorateur sélectionnez la connexion order_entry et tirez la sur la Forme | |
posez un Button sur la Forme et créez son clic. Tapez les instructions qui modifient des lignes, par exemple en "Smith" en "Martin":
const k_update_invoice=
'UPDATE invoice' + k_new_line
+ ' SET i_customer= ''Martin''' + k_new_line
+ ' WHERE i_customer= ''Smith''';
procedure TWinForm.update_invoice__Click(sender: System.Object;
e: System.EventArgs); begin
BdpConnection1.Open();
execute_non_query(do_execute_.Checked, k_update_invoice);
BdpConnection1.Close(); end; // update_invoice__Click |
| | compilez, exécutez et cliquez "update_invoice_" | |
voici une vue du projet:  |
4.4.7 - Requête paramétrée Lorsque nous envoyons une requête complexe (ce qui n'a pas été le cas
ci-dessus), le Serveur essayera d'optimiser l'ordre des opérations. Cette optimisation, pour des requêtes impliquant de nombreuses TABLEs, peut prendre des heures. Il est alors recommandé de procéder en 2 temps:
- nous envoyons un requête contenant des paramètres qui ne sont pas encore spécifiés en utilisant une commande Prepare() :
- le Serveur calcule un ordre optimal des traitements :
- lorsque le Client souhaite effectuer le traitement, il envoie les valeurs
des paramètres au Serveur, qui dispose alors d'une requête complète
- le serveur exécute le traitement
Il faudra donc
- envoyer une requête incomplète, avec des paramètres (comme des sortes de variables)
- pour exécuter la requête:
- donner des valeurs littérales à ces paramètres
- exécuter la requête
Pour le BDP, les paramètres de la requête sont désignés par "?". Par exemple:
UPDATE invoice SET i_customer= ?
WHERE i_id= ? | Et pour fournir la valeur des paramètres, nous utilisons la propriété
Parameters de SqlCommand:
var my_c_parameter: BdpParameter;
my_c_parameter:= BdpCommand.Parameters.Add('i_customer', BdpType.String);
my_c_parameter.Value:= 'PeterCo';
my_c_parameter:= BdpCommand.Parameters.Add('i_id', BdpType.Int32);
my_c_parameter.Value:= 101; | Notez que la notation pour désigner les paramètres dépend des composants
d'accès ("?" pour BdpCommand, mais @my_param pour SqlCommand). Et les types sont aussi différents (BdpType.Int32, mais FbDbType.VarChar pour FireBird).
Voici le code complet
| ajoutez un Button, nommez-le "prepare_", créez son événement clic et placez-y le code qui ouvre une connection (variable globale) et prépare une requête :
const k_parametrized_update_request=
'UPDATE invoice '
+ ' SET i_customer= ?'
+ ' WHERE (i_id= ?) ' ;
var g_c_bdp_command: BdpCommand= Nil;
procedure TWinForm.prepare__Click(sender: System.Object;
e: System.EventArgs); begin
g_c_bdp_command:= BdpCommand.Create(k_parametrized_update_request, BdpConnection1);
BdpConnection1.Open(); g_c_bdp_command.ParameterCount:= 2;
g_c_bdp_command.Prepare(); end; // prepare__Click |
| | ajoutez un autre Button, appelez-le "execute_" et exécutez la requête dans sa méthode clic:
procedure TWinForm.execute__Click(sender: System.Object;
e: System.EventArgs);
var l_c_bdp_parameter: BdpParameter; begin
// -- create, add and initialize the parmeters
l_c_bdp_parameter:= g_c_bdp_command.Parameters.Add('i_customer',
BdpType.String);
if l_c_bdp_parameter= Nil
then display_bug_stop('not_added');
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 |
| | compilez, exécutez, cliquez "list_", "prepare_", "execute_" et "list_" |
| voici le résultat:  |
Au lieu de créer la commande et les paramètres par code, nous pouvons aussi utiliser le composant BdpCommand situé sur la Palette: |
sélectionnez la page "Borland Data Provider" de la Palette | | sélectionnez BdpCommand:
et posez-le sur la Forme | |
sélectionnez la propriété Connection de BdpCommand1 et initialisez la à BdpConnection1 | |
ajoutez la requête en utilisant l'éditeur de commandes: - sélectionnez la propriété CommandText, et cliquez l'ellipse "...'
- l'éditeur de commande est affiché:
- sélectionnez INVOICE, sélectionnez "Command Type | Update", cliquez "Generate SQL" et corrigez la requête pour qu'elle corresponde à celle présentée ci-dessus
 | | créez et initialisez les paramètres: | |
ajoutez un Button qui appelle Prepare:
procedure TWinForm.prepare_2__Click(sender: System.Object;
e: System.EventArgs); begin
BdpConnection1.Open(); BdpCommand1.Prepare();
end; // prepare_2__Click | | |
ajoutez un autre Button, appelez-le "execute_2_" et exécutez la requête dans sa méthode clic:
procedure TWinForm.execute_2__Click(sender: System.Object;
e: System.EventArgs); begin
BdpCommand1.Parameters[0].Value:= customer_text_box_.Text;
BdpCommand1.Parameters[1].Value:= Convert.ToInt32(id_text_box_.Text);;
BdpCommand1.ExecuteNonQuery(); end; // execute_2__Click |
| | compilez, exécutez, cliquez "list_", "prepare_", "execute_" et "list_" |
| voici le résultat:  |
Nous aurons encore l'occasion de retrouver les requêtes paramétrées pour la mise à jour depuis des contrôles visuels. Vous trouverez le code source dans p_23_bdp_update_table.zip
4.5 - Afficher des données 4.5.1 - SqlDataReader Pour afficher le contenu d'une TABLE, nous devons récupérer les données du Serveur. Pour cela, nous utilisons:
- un BdpConnection
- une commande BdpCommand
- un iDataReader qui nous est fourni par la commande de lecture
- éventuellement des contrôles pour afficher
En rouge sur notre schéma d'architecture, le DataReader et une TextBox: La requête qui effectue la lecture des lignes est SELECT:
SELECT i_id, i_customer, i_date
FROM invoice WHERE i_id< 102 |
Au niveau fonctionnement: - le Client envoie la requête de lecture en préparant un tampon pour recevoir les données de la réponse. Au niveau de la syntaxe ADO .Net
- nous utilisons un BdpCommand
- nous appelons la fonction ExecuteReader()
- cette fonction retourne un objet SqlDataReader() qui sera utilisé pour lire les lignes
- le Serveur calcule, à partir d'une ou plusieurs TABLES, la table résultat ("Result Dataset") et la renvoie au Client
- le SqlDataReader effectue les traitements qu'il souhaite, par exemple afficher les données dans un TextBox
Voici comment procéder:
| créez un nouveau projet, et appelez-le "p_31_bdp_display_data_reader" | |
posez un Button sur la Forme, nommez-le "display_", créez sa méthode clic et tapez le code qui appelle ExecuteReader():
procedure TWinForm.display_invoice__Click(sender: System.Object;
e: System.EventArgs);
var l_c_command: BdpCommand;
l_c_reader: iDataReader;
l_row_index: Integer;
l_column_index: Integer;
l_display: String; begin
l_c_command:= BdpConnection1.CreateCommand();
l_c_command.CommandText:= k_select_training;
BdpConnection1.Open();
l_c_reader:= l_c_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).ToString;
display(l_row_index.ToString+ ':'+ l_display);
Inc(l_row_index); end; // while
BdpConnection1.Close(); end; // display_invoice_click |
| | compilez et exécutez | | voilà le résultat:
 |
Mentionnons que nous avions déjà utilisé des DataReaders dans les projets précédents:
- lors de la création de TABLEs, pour afficher le schéma
- lors du remplissage / effacement / modification, pour vérifier le résultat de nos traitements
Ces emplois de DataReader se trouvent dans les .ZIP, mais nous n'avions pas
détaillé les traitements.
4.5.2 - Affichage dans un DataGrid par code Nous allons afficher les données de la table INVOICE dans un DataGrid.
Pour cela nous allons être obligés d'utiliser toute la batterie des composants ADO .Net: - BdpConnection, BdpCommand pour récupérer les données
- DataAdapter pour pomper les données dans un DataSet en appelant Fill()
- le DataSet qui contiendra un DataTable où seront stockées TOUTES les lignes
- un DataGrid pour afficher
Soit, avec notre vue architecturale: Au niveau code: - nous mettons les composants en place:
- BdpCommand contient simplement le SELECT
- un BdpDataAdapter est créé et notre BdpCommand lui est relié
- un DataSet vide est créé
- nous chargeons les données
- en ouvrant la connexion
- en appelant BdpDataAdapter.Fill()
- nous pouvons fermer la connexion
- nous relions la table unique du DataSet à une DataGrid
Par conséquent:
| créez un nouveau projet, et appelez-le "p_32_display_datagrid_code" | |
dans l'Explorateur sélectionnez la connexion "order_entry" et tirez ma sur la Forme | |
de l'onglet DataControls de la Palette, sélectionnez le DataGrid: et posez sur la Forme |
| posez un Button sur la Forme, nommez-le "data_table_", créez sa méthode clic et tapez le code qui appelle ExecuteReader():
const k_select_invoice= 'SELECT * FROM invoice';
procedure TWinForm.adapter__Click(sender: System.Object;
e: System.EventArgs);
var l_c_command: BdpCommand;
l_c_data_adapter: BdpDataAdapter;
l_c_data_set: Dataset;
l_c_data_table_invoice_ref: DataTable; begin
l_c_command:= BdpCommand.Create(k_select_invoice, BdpConnection1);
l_c_data_adapter:= BdpDataAdapter.Create;
l_c_data_adapter.SelectCommand:= l_c_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];
// -- view a single table in the dbGrid
DataGrid1.DataSource:= l_c_data_table_invoice_ref;
end; // adapter__Click | | |
compilez, exécutez et cliquez "adapter_" | | voilà le résultat:  |
Quelques commentaires: - tous les objets (sauf la BdpConnection) sont créés en local, ce qui permet de bien voir comment et quand ils sont utilisés
- la connexion n'a besoin d'être ouverte que pour le chargement du DataSet. Une fois les données rapatriées en mémoire, la connexion peut être fermée
- c'est FILL qui est l'instruction clé. Elle sera nécessaire, explicitement
ici, ou implicitement via un Borland Data Adapter ailleurs.
Notez aussi que, contrairement à dbExpress où le "sens" du chaînage des composants est uniforme:
DataGrid -> tDataSource -> tClientDataset -> tDataProvider -> tSqlQuery -> tBdpConnection ici Fill() inverse le chaînage:
DataGrid -> DataTable -> DataTable <== DataAdapter -> tBdpCommand -> tBdpConnection
- si notre commande contenait plusieurs TABLES (SELECT multiples séparés par des ";") :
SELECT * FROM invoice ; SELECT * FROM item
| alors le DataSet contiendrait plusieurs DataTable. Ici nous avons utilisé un SELECT sur une TABLE. Mentionnons que seul les providers liés à SQL Server permettent ces
requêtes multiples Mais le fait important est qu'Ado .Net comporte un DataSet (un SET de DATA tables) et que cet élément est nouveau par rapport aux composants dbExpress
Et ceci explique pourquoi nous avons du utiliser DataSet.Tables[0] - finalement nous relions le DataGrid à notre DataTable. Nous aurions aussi pu relier la DataGrid au DataSet:
procedure TWinForm.dataset__Click(sender: System.Object;
e: System.EventArgs);
var l_c_command: BdpCommand;
l_c_data_adapter: BdpDataAdapter;
l_c_data_set: Dataset; begin
l_c_data_adapter:= BdpDataAdapter.Create;
l_c_data_adapter.SelectCommand:= BdpCommand.Create(k_select_invoice, BdpConnection1);;
l_c_data_set:= DataSet.Create('my_invoices');
BdpConnection1.Open();
l_c_data_adapter.Fill(l_c_data_set);
BdpConnection1.Close();
DataGrid1.DataSource:= l_c_data_set;
end; // dataset__Click | avec le résultat que voici: nous cliquons sur "+": et enfin nous cliquons sur "Table": Notez le titre "my_invoice" que nous avions fourni lors de la création du DataSet, ainsi que l'icône de navigation en haut à droite du DataGrid. Cette structure arborescente avec une seule TABLE est totalement sans
intérêt ici, et c'est pourquoi nous avons relié la DataGrid à DataSet.Tables[0]
4.5.3 - Affichage par composants Voici à présent comment effectuer le même traitement en utilisant les
composants Bdp_xxx pris sur la Palette:
Notez que
- l'instruction BdpDataAdapter1.Fill() est invoquée pendant la conception, ce qui permet de visualiser les données réelles de la Table. Ceci ne fonctionne que pour le BdpDataAdapter (pas le SqlDataAdapter ou le
FbDataAdapter)
4.5.4 - Relation Maître Détail Nous allons réaliser, en utilisant les composants de la Palette, un affichage Maître Détail de 2 tables:
- une première DataGrid affichera les factures
- une seconde DataGrid affichera les articles de cette commande
Notez que: - le projet contient aussi un exemple de jointure SQL des deux tables, et un
bouton qui permet de calculer la relation par code
4.6 - DataSet en mémoire 4.6.1 - Architecture du DataSet
Nous allons nous pencher sur les traitements effectués au niveau du DataSet: remplir, modifier, trier etc. Nous pourrions utiliser un DataSet connecté et rempli par un DataAdapter, comme nous l'avons fait ci-dessus. Il est plus
instructif d'utiliser un DataSet autonome, que nous construirons et manipulerons par code uniquement. Mais souvenez-vous que tous les traitements pourraient être réalisés sur un DataSet rempli depuis des TABLEs du Serveur.
Nous sommes donc au niveau du DataSet: Le DataSet est représenté dans la documentation MSDN comme un aggrégat contenant
- une collection de DataTable
- une collection de DataRelation
- une collection de DataView
Pour le moment, nous allons effectuer des traitements des DataTable par code:
Voyons cela de plus près.
4.6.2 - Création d'une DataTable par code
Nous nous créer une DataTable avec des factures (identificateur, nom). Il suffit, après la création de la DataTable de créer chaque colonne, en spécifiant son type avec éventuellement des attributs.
Voici comment créer la table en mémoire | créez un nouveau projet, et appelez-le "p_41_bdp_create_in_memory" |
| posez un Button sur la Forme, nommez-le "create_data_table_", créez sa méthode clic et tapez le code qui créé la DataTable:
var g_c_invoice_data_table: DataTable;
procedure TWinForm.create_data_table__Click(sender: System.Object;
e: System.EventArgs);
var l_c_data_column: DataColumn; begin
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 | | |
compilez et exécutez |
Pour suivre ce qui se passe, nous avons aussi connecté quelques événements de la DataTable. Le lecteur attentif aura détecté les deux Include(...) à la fin
de notre procédure de création. Voici comment ajouter les méthodes référencées par Include. Les événements sont RowChanged et ColumnChanged. Pour connaître la liste des
événements et leur déclaration, nous avons utilisé l'aide .Net. Pour cela, nous sélectionnons dans le programme un mot (DataTable, par exemple) et tapons F1. L'aide est présentée à la racine:
Nous sautons les premiers dossiers, le seul nous concernant étant Reference Class Library
Et là nous trouvons tous les NameSpaces, et en particulier Reference Class Library
System.Data DataTable Class
Et voici le code de nos événements: |
déclarez les événements RowChanged et EventChanged dans la tWinForm: type
TWinForm = class(System.Windows.Forms.Form)
// -- ... public
constructor Create;
procedure row_changed(sender: tObject;
e: System.Data.DataRowChangeEventArgs);
procedure column_changed(sender: tObject;
e: System.Data.DataColumnChangeEventArgs);
end; | et écrivez le code d'affichage (f_display_datarow() affiche le contenu d'une ligne et est placée dans une unité d'utilitaires qui est contenue
dans le .ZIP)
procedure TWinForm.row_changed(sender: tObject;
e: System.Data.DataRowChangeEventArgs);
var l_action_name: System.String; begin
display(System.String.Format( | | |