Firebird ADO Net Tutorial - John COLIBRI. |
- résumé : Programmation d'applications ADO Net Firebird
- mots clé : Firebird - ADO.Net - Delphi - Windows Forms
- logiciel utilisé : Windows XP Home - .Net SDK 1.1.432 - Firebird 1.5.2 -
Firebird .Net Provider 1.7.1 - Delphi 2005
- 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:
Nous avons déjà organisé pour certains clients des formations ADO.Net uniquement (3 jours), et envisageons de la placer au catalogue. Ceux intéressés peuvent nous contacter à jcolibri@jcolibri.com.
2 - Principe 2.1 - Architecture Client Serveur Les applications de bases de données, que ce soit Firebird, MySql, Sql
Server, Oracle 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 Firebird pour nous)
- les couches réseau
- un ou plusieurs Clients contiennent
- les couches réseau (Tcp/Ip ici)
- le client (le Client Firebird 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 Firebird
- le Client Firebird
- 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 le Serveur et le Client Firebird. Pour les experts - vous cherchez le logiciel via Google
- vous le téléchargez
- vous lancez l'installation en cliquant "oui" partout
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. 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, mais pas Firebird. Il faut installer le DataProvider Firebird (une sorte de pilote .Net pour Firebird). Nous avons
présenté comment installer le DataProvider Firebird. Très rapidement - vous cherchez le logiciel via Google
- vous le téléchargez
- vous lancez l'installation en cliquant "oui" partout
Si cela ne fonctionne pas, voyez l'article cité qui explique tout par le menu détail, avec les tests à toutes les étapes.
2.2 - Organisation des répertoires
Nous allons utiliser la base de données de départ EMPLOYEE.FDB. Pour éviter de naviguer dans "Program Files", nous avons copié cette base dans le répertoire c:\programs\_data\ et nous utiliserons donc la base
c:\programs\_data\EMPLOYEE.FDB
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 connection et exécuter les requêtes SQL. Ces composants forment le DataProvider
et: - FbConnection permet d'établir la connection avec le Serveur
- FbCommand est utilisé pour lancer les requêtes (SELECT ou CREATE, INSERT etc)
- FbDataReader 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 DataAdapter. Il contient essentiellement
- quatre FbCommand, 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 DataAdapter
- 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 DataAdapter que nous avons présenté ci dessus permet
- le chargement des données depuis le Serveur dans le DataSet: c'est l'instruction DataAdapter.Fill()
- la sauvegarde des modifications effectuées dans les contrôles visuels: c'est l'instruction DataAdapter.Update()
Que vient faire le Firebird DataProvider dans cette affaire ? Eh bien le .Net Framework et Delphi sont livrés avec des DataProvider standards tels que - un provider pour Sql Server
- 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é
Dans notre cas, le Firebird DataProvider est simplement un composant qui implémente les interface du DataProvider pour arriver à communiquer avec Firebird (plutôt qu'avec Sql Server ou Access).
Il existe aussi un BDP DataProvider, qui est une adaptation du BDP pour Firebird
Dans cet article, nous nous intéresserons à la programmation directe ADO.Net,
et présenterons l'utilisation du Firebird DBP ailleurs.
4 - La programmation ADO.Net
4.1 - La connection
Nous allons commencer par établir la connection entre notre application Delphi et le Serveur. Nous utiliserons le composant FbConnection:
4.1.1 - Connection - composant Les articles précédents ont décrits les tests que vous pouvez effectuer tout au long de l'installation. En final, il faut pouvoir se connecter depuis Delphi
au Serveur. C'est le test que nous allons effectuer à présent. | lancez Delphi |
| créez une nouvelle application Windows Forms "file | New | Delphi Windows Forms" | |
fermez les onglets de la palette, et ouvrez le dernier "general" | | les 4 composants Firebird DataProvider sont affichés (en bas à droite)
| | sélectionnez FbConnection
et tirez le sur la Forme | |
Delphi dépose FbConnection1 dans la zone des composants non visuels:
ATTENTION: il faut tirer FbConnection sur la Forme, et Delphi le dépose dans la zone non-visuelle | | cliquez sur la Forme |
| les propriétés apparaissent dans l'Inspecteur d'Objet | |
dans l'Inspecteur d'Objet, sélectionnez sa propriété "Connection" et cliquez l'ellipse | | l'éditeur de connection est affiché
| | saisissez le nom de votre serveur
localhost et le chemin de votre base de données c:\programs\_dataemployee.fdb | |
le dialogue avant le test: | | cliquez "Test" |
| la connection est établie |
Notez que:
4.1.2 - Connection - code
Voici comment établir cette connection en programmant toutes les étapes: | lancez Delphi et créez une nouvelle application Windows Forms
| | Delphi crée un nouveau projet |
| sauvegardez le projet sous p_00_connect et l'unité sous u_00_connect dans un répertoire 01_connection |
| éventuellement définissez le répertoire où vous souhaitez placer l'.EXE et les .DCU, en sélectionnant "Projects | Options" | |
voici le dialogue de sélection des chemins | |
posez un tButton sur la Forme, donnez à sa propriété Text le titre "connect_" et à sa propriété (Name) le nom "connect_" Créez son événement OnClick |
| tapez le code de connection:
const k_database_path= 'c:\programs\_data\';
k_file_name= 'employee.fdb';
k_user_password= 'User=SYSDBA;Password=masterkey';
k_other= 'Dialect=3;Server=localhost'; k_connection_string=
'Database='+ k_database_path+ k_file_name
+ ';'+ k_user_password+ ';'+ k_other;
var g_c_connection: FbConnection;
procedure TWinForm.connect__Click(sender: System.Object;
e: System.EventArgs); begin
g_c_connection:= FbConnection.Create(k_connection_string);
g_c_connection.Open(); Text:= 'connection ok';
end; // connect__Click | | |
importez la DLL Firebird, en ajoutant au USES (de l'IMPLEMENTATION): IMPLEMENTATION
USES FirebirdSql.Data.Firebird; |
Si vous compilez maintenant, vous auriez une erreur de compilation "file not found, FirebirdSql.Data.Firebird.dcuil" Pour que notre projet inclue la DLL, il faut, en plus du USES
traditionnel, ajouter une "directive .Net" qui importe la .DLL. Pour cela : | | cliquez le menu "Project | Add Reference"
| | Delphi ouvre le dialogue présentant les Assemblies .Net connues
| | sélectionnez FirebirdSql.Data.Firebird
| | cliquez "Add Reference"
ATTENTION: si vous ne cliquez pas, rien ne sera pris en compte Puis cliquez "OK" | | Delphi ajoute une directive .Net au .DPR |
| pour voir la références, affichez le source du .DPR en cliquant "Project | View Source" |
| le .DPR contient à présent une directive .Net: | | compilez et exécutez
| | voici le résultat |
Notez que
- Nous avons placé la chaîne de connection dans plusieurs constantes. Nous aurions, naturellement pu placer tout dans la même constantes, ou encore dans l'appel FbConnection.Create('xxx').
- pour le nom de la base, la syntaxe est :
serveur ":" lettre_disque ":\" chemin nom_fichier Par exemple: 127.0.0.1:c:\gestion\stock\Institut_Pascal.fdb
HostName:c:\ventes\export.fdb www.jcolibri.com:c:\programs\_data\employee.fdb
Notez que pour Interbase il fallait ajouter un \ entre le nom du serveur et celui du chemin: localhost\c:\gestion\interbase\Institut_Pascal.gdb Pour Firebird, le ":" a remplacé le "\"
- .Net Framework n'utilise pas le préfixe t pour désigner les CLASSes. Les INTERFACEs sont bien préfixées par i, mais les CLASSes ont perdu le t.
Cela doit provenir du Java, ou peut-être même du VB. Nous utiliserons tout de même, lorsque cela améliore la présentation, le t habituel (mais il ne sera pas présent dans le code, bien sûr). Un bouton de type tButton appellé
Button1 ayant un titre "Button1". - ATTENTION, par défaut Delphi met tout dans un répertoire de "Program Files", et il faut faire très attention que même si vous avez désigné le
répertoire pour le .DPR, il faut A NOUVEAU spécifier le répertoire pour l'unité. Il y a certainement un moyen de forcer Delphi à utiliser un répertoire par défaut autre que "Program Files", mais je ne l'ai pas trouvé
- ATTENTION, le fait de nommer (Name) d'un contrôle ne modifie pas automatiquement Text
- Delphi 2005 ajoute à présent automatiquement un "_" entre le nom du
contrôle et le "Click". Avec un tButton "Button1", si vous cliquez deux fois dessus, l'événement sera appelé "Button1_Click". Et si vous appelez le tButton "Button1_", l'événement sera "Button1__Click". Personnellement
j'ajoutais toujours le "_" en Delphi 6:
- Button1_Click est plus lisible pour moi que Button1Click. Des goûts et des couleurs ...
- de plus le "_" me protégeait des conflits de noms. Quelqu'un qui
nommerait un tButton "connect" dans une applications Socket, a quelques soucis à se faire
Pour télécharger le source, cliquez p_01_connect.zip
4.2 - Création de table
4.2.1 - CREATE TABLE Nous allons créer une nouvelle table qui contiendra les formations que nous offrons. Cette table est définie par: - un identificateur unique
- le titre de la formation
- la durée en jours
- le prix
Nous créons cette table, nous enverrons vers Firebird la requête SQL suivante:
CREATE TABLE training (
t_id INTEGER , t_name CHARACTER(24)
, t_days INTEGER , t_price NUMERIC(5, 2)
) | Notez que le préfixe "t_" pour désigner les champs de Training n'est pas nécessaire.
Pour envoyer cette requêtes vers le Serveur, nous utiliserons les composants suivants: - FbConnection pour la connection
- FbCommand 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.2.2 - La création Par conséquent:
| de l'onglet général, sélectionnez fbConnection et posez-le sur la Forme | |
dans l'Inspecteur d'Objet, sélectionnez ConnectionString, cliquez l'ellipse | | Delphi ouvre l'éditeur de connection |
| sélectionnez Browse, et placez-y votre base de données Cliquez "Accept" (pour sauvegarder vos sélections), puis rouvrez-le et cliquez "Test" |
| la connection est confirmée: | |
posez un tButton sur la Forme, nommez-le "create_training_", créez son événement OnClick et tapez le code de création:
const k_create_training_table=
'CREATE TABLE training '+ k_new_line
+ ' ('+ k_new_line
+ ' t_id INTEGER'+ k_new_line
+ ' , t_name CHARACTER(24)'+ k_new_line
+ ' , t_days INTEGER'+ k_new_line
+ ' , t_price NUMERIC(5, 2)'+ k_new_line
+ ' )';
procedure TWinForm.create_training__Click(sender: System.Object;
e: System.EventArgs);
var l_c_command: FbCommand;
l_result: Integer; begin
FbConnection1.Open();
l_c_command:= FbCommand.Create(k_create_training_table, FbConnection1);
l_c_command.ExecuteNonQuery(); FbConnection1.Close();
end; // create_training_Click | |
4.2.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. Voici le résultat de l'exécution de CREATE TABLE:
Notez que :
Vous pouvez télécharger le projet p_21_create_table.zip
4.2.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. Pour ajouter un nouveau stage, la requête SQL est la suivante:
INSERT INTO training
(t_id, t_name, t_days, t_price)
VALUES (101, 'ASP .Net', 3, 1400) |
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. Voici la procédure:
procedure fill_the_training(p_id: Integer; p_name: System.String;
p_days: Integer; p_price: Double);
var l_values, l_request: System.String;
begin l_values:= p_id.ToString
+ ', '''+ p_name+ ''''
+ ', '+ p_days.ToString
+ ', '+ f_replace_character(p_price.ToString, ',', '.');
l_request:= 'INSERT INTO training '
+ ' (t_id, t_name, t_days, t_price) '+ k_new_line
+ ' VALUES ('+ l_values+ ')';
execute_non_query(do_execute_.Checked, l_request);
end; // fill_the_training | et comme l'envoi de la requête d'écriture utilise toujours la même instruction
ExecuteNonQuery, nous avons choisi d'utiliser une autre procédure Delphi qui sera employée pour toutes nos requêtes d'écriture:
procedure TWinForm.execute_non_query(p_do_execute: Boolean;
p_request: System.String);
var l_c_command: FbCommand;
l_count: Integer; begin
if p_do_execute then begin
l_c_command:= FbCommand.Create(p_request,
FbConnection1);
l_count:= l_c_command.ExecuteNonQuery();
end; end; // execute_non_query |
Par conséquent: | créez un nouveau projet et nommez-le p_22_fill_table | |
posez un FbConnection sur la Forme et initialisez ConnectionString | |
posez un tButton 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_training__Click(sender: System.Object; e: System.EventArgs);
procedure fill_the_training(p_id: Integer; p_name: System.String;
p_days: Integer; p_price: Double);
var l_values, l_request: System.String;
begin l_values:= p_id.ToString
+ ', '''+ p_name+ ''''
+ ', '+ p_days.ToString
+ ', '+ f_replace_character(p_price.ToString, ',', '.');
l_request:= 'INSERT INTO training '
+ ' (t_id, t_name, t_days, t_price) '+ k_new_line
+ ' VALUES ('+ l_values+ ')';
execute_non_query(do_execute_.Checked, l_request);
end; // fill_the_training begin // fill_training__Click
FbConnection1.Open();
fill_the_training(100, 'ADO.Net', 3, 1400);
fill_the_training(101, 'ASP.Net', 3, 1400);
fill_the_training(102, 'UML_desing_patterns', 3, 1400);
fill_the_training(103, 'Interbase_Client_Server', 3, 1400);
fill_the_training(104, 'Delphi_2006', 5, 2300);
FbConnection1.Close(); end; // fill_training__Click |
| 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 (Firebird Dialecte 3)
- 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
4.2.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 103, nous utiliserions:
DELETE FROM training
WHERE t_id= 103 | Comme dans notre cas nous souhaitons purger TOUTES les lignes, nous supprimons
simplement la clause WHERE (effacement inconditionnel). Donc | ajoutez un tButton, 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_training= 'DELETE FROM training';
procedure TWinForm.delete_training__Click(sender: System.Object;
e: System.EventArgs); begin
FbConnection1.Open();
execute_non_query(do_execute_.Checked, k_delete_all_training);
FbConnection1.Close(); end; // delete_training__Click |
|
Vous trouverez ce projet dans p_22_fill_table.zip
4.2.6 - Modifier une donnée Pour modifier la valeur de données, nous utilisons UPDATE:
UPDATE training
SET t_price= 1450 WHERE t_price= 1400 |
Nous avons placé le code dans un nouveau projet: | créez un nouveau projet et nommez-le p_23_update_table |
| posez un FbConnection sur la Forme et initialisez ConnectionString | |
posez un tButton sur la Forme et créez son clic. Tapez les instructions qui modifient des lignes, par exemple en changeant tous les prix de 1.400 à 1.450:
const k_update_training=
'UPDATE training' + k_new_line
+ ' SET t_price= 1450' + k_new_line
+ ' WHERE t_price= 1400';
procedure TWinForm.update_training__Click(sender: System.Object;
e: System.EventArgs); begin
FbConnection1.Open();
execute_non_query(do_execute_.Checked, k_update_training);
FbConnection1.Close(); end; | |
4.2.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
Les paramètres sont désignés par "@ identificateur". Par exemple:
UPDATE training
SET t_price= t_price + @delta_price
WHERE (t_id= @old_id) |
Et pour fournir la valeur des paramètres, nous utilisons la propriété Parameters de SqlCommand:
my_c_parameter:= my_c_command.Parameters.Add('@old_id', FbDbType.Integer);
my_c_parameter.Value:= 303; |
Voici le code complet |
ajoutez un tButton, 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 training '
+ ' SET t_price= t_price + @delta_price'
+ ' WHERE (t_id= @old_id) ' ;
var g_c_command: FbCommand;
procedure TWinForm.prepare__Click(sender: System.Object;
e: System.EventArgs); begin
g_c_command:= FbCommand.Create(k_parametrized_update_request,
FbConnection1);; // -- send the request
FbConnection1.Open(); g_c_command.Prepare();
end; // prepare__Click | | |
ajoutez un autre tButton, 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_parameter: FbParameter; begin
// -- initialize the parmeters
l_c_parameter:= g_c_command.Parameters.Add('@old_id',
FbDbType.Integer);
l_c_parameter.Value:= Convert.ToInt32(id_text_box_.Text);
l_c_parameter:= g_c_command.Parameters.Add('@delta_price',
FbDbType.Integer);
l_c_parameter.Value:= Convert.ToInt32(amount_delta_text_box_.Text);
g_c_command.ExecuteNonQuery(); end; // execute__Click |
| | compilez et exécutez |
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_update_table.zip
4.3 - Afficher des données
4.3.1 - SqlDataReader Pour afficher le contenu d'une TABLE, nous devons récupérer les données du Serveur. Pour cela, nous utilisons: - un FbConnection
- une commande FbCommand
- un iDataReader qui nous est fourni par la commande de lecture
- éventuellement des contrôles pour afficher
En rouge le DataReader et une TextBox:
La requête qui effectue la lecture des lignes est SELECT:
SELECT t_id, t_name, t_days, t_price
FROM training WHERE t_price< 2000 |
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 SqlCommand
- 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_30_display_data_reader | |
posez un tButton sur la Forme, nommez-le "display_", créez sa méthode clic et tapez le code qui appelle ExecuteReader():
const k_database_path= 'c:\programs\_data\';
k_file_name= 'employee.fdb';
k_user_password= 'User=SYSDBA;Password=masterkey';
k_other= 'Dialect=3;Server=localhost';
k_connection_string= 'Database='+ k_database_path+ k_file_name
+ ';'+ k_user_password+ ';'+ k_other;
k_select_training= 'SELECT * FROM training';
procedure TWinForm.display__Click(sender: System.Object;
e: System.EventArgs);
var l_c_connection: FbConnection;
l_c_command: FbCommand;
l_c_reader: iDataReader;
l_row_index: Integer;
l_column_index: Integer;
l_display: String; begin
l_c_connection:= FbConnection.Create(k_connection_string);
l_c_command:= l_c_connection.CreateCommand();
l_c_command.CommandText:= k_select_training;
l_c_connection.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 3- 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
l_c_connection.Close(); end; // display__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.3.2 - Affichage dans un DataGrid par code Nous allons afficher les données de la table TRAINING dans un DataGrid.
Pour cela nous allons être obligés d'utiliser toute la batterie des composants ADO.Net: - SqlConnection, SqlCommand pour récupérer les données
- tDataAdapter pour pomper les données dans un tDataSet en appelant Fill()
- le tDataSet qui contiendra un tDataTable où seront stockées TOUTES les lignes
- un tDataGrid pour afficher
Soit: Au niveau code: - nous mettons les composants en place:
- FbCommand contient simplement le SELECT
- un FbDataAdapter est créé et notre FbCommand lui est relié
- un DataSet vide est créé
- nous chargeons les données
- en ouvrant la connection
- en appelant FbDataAdapter.Fill()
- nous pouvons fermer la connection
- nous relions la table unique du tDataSet à une tDataGrid
Par conséquent: |
créez un nouveau projet, et appelez-le p_33_datagrid_code | | de l'onglet DataControls de la Palette, sélectionnez le DataGrid:
et posez sur la Forme | |
posez un tButton sur la Forme, nommez-le "data_adapter_", créez sa méthode clic et tapez le code qui appelle ExecuteReader():
const k_database_path= 'c:\programs\_data\';
k_file_name= 'employee.fdb';
k_user_password= 'User=SYSDBA;Password=masterkey';
k_other= 'Dialect=3;Server=localhost';
k_connection_string= 'Database='+ k_database_path+ k_file_name
+ ';'+ k_user_password+ ';'+ k_other;
k_select_training= 'SELECT * FROM training';
procedure TWinForm.adapter__Click(sender: System.Object;
e: System.EventArgs);
var l_c_connection: FbConnection;
l_c_command: FbCommand;
l_c_data_adapter: FbDataAdapter;
l_c_data_set: Dataset;
l_c_data_table_training_ref: DataTable; begin
l_c_connection:= FbConnection.Create(k_connection_string);
l_c_command:= FbCommand.Create(k_select_training, l_c_connection);
l_c_data_adapter:= FbDataAdapter.Create;
l_c_data_adapter.SelectCommand:= l_c_command;
l_c_data_set:= DataSet.Create('my_trainings');
l_c_connection.Open();
l_c_data_adapter.Fill(l_c_data_set);
l_c_connection.Close();
l_c_data_table_training_ref:= l_c_data_set.Tables[0];
// -- view a single table in the dbGrid
DataGrid1.DataSource:= l_c_data_table_training_ref;
end; // adapter__Click | | |
compilez, exécutez et cliquez "adapter_" | | voilà le résultat: |
Quelques commentaires: - tous les objets sont créés en local, ce qui permet de bien voir comment et quand ils sont utilisés
- la connection n'a besoin d'être ouverte que pour le chargement du tDataSet.
Une fois les données rapatriées en mémoire, la connection 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: tDataGrid -> tDataSource -> tClientDataset -> tDataProvider ->
tSqlQuery -> tSqlConnection ici Fill() inverse le chaînage: tDataGrid -> tDataTable -> tDataTable <== tDataAdapter ->
tFbCommand -> tFbConnection - si notre commande contenait plusieurs TABLES (SELECT multiples séparés par des ";") :
SELECT * FROM training ; SELECT * FROM students
| alors le DataSet contiendrait plusieurs DataTable. Ici nous avons utilisé un SELECT sur une TABLE.
Mentionnons que le Firebird Data Provider (ou celui d'Interbase) ne permettent pas ces requêtes multiples dans un FbCommand (mais Sql Server le permet).
Mais le fait important est qu'Ado.Net comporte un tDataSet (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 tDataSet.Tables[0] - finalement nous relions le tDataGrid à notre DataTable. Nous aurions aussi pu relier la tDataGrid au tDataSet:
DataGrid1.DataSource:= l_c_data_set; |
avec le résultat que voici: nous cliquons sur "+":
et enfin nous cliquons sur "Table": Notez le titre "my_trainings" que nous avions fourni lors de la création du
tDataSet, ainsi que l'icône de navigation en haut à droite du tDataGrid. Cette structure arborescente avec une seule TABLE est totalement sans intérêt ici, et c'est pourquoi nous avons relié la tDataGrid à
tDataSet.Tables[0]
4.3.3 - Affichage par composants Voici à présent comment effectuer le même traitement en utilisant les composants Fb_xxx:
| créez un nouveau projet, et appelez-le p_31_display_datagrid | |
posez un FbConnection sur la Forme et initialisez ConnectionString, testez et confirmez | |
dans l'onglet General de la Palette sélectionnez un FbDataAdapter tirez-le sur la Forme |
| Delphi le dépose dans la zone des composants et ouvre un Wizzard | |
fermez le Wizard. On sait faire tout seul. | | cliquez sur la Forme, puis cliquez sur FbDataAdapter1 |
| l'Inspecteur d'Objet présente les propriétés de FbDataAdapter Voici la vue après avoir cliqué SelectCommand
Notez que l'Inspecteur comporte au bas un Panel qui affiche une sorte de guide (que faire) |
| sélectionnez SqlConnection et cliquez deux fois pour y placer FbConnection1 | |
sélectionnez CommandText et cliquez l'ellipse | | Delphi ouvre l'Editeur de commandes:
| | tapez la commande de sélection
| | cliquez "Execute" pour vérifier la syntaxe |
| l'Editeur affiche bien la TABLE: |
| cliquez "Accept" pour fermer l'Editeur | |
dans l'onglet "Data Components" de la Palette sélectionnez un DataSet tirez-le sur la Forme |
| sélectionnez une tDataGrid sur la Forme | |
posez un tButton sur la Forme, nommez-le "adapter_fill_", créez sa méthode clic et tapez le code qui appelle FbDataAdapter.Fill() :
procedure TWinForm.adapter_fill__Click(sender: System.Object;
e: System.EventArgs); begin
FbDataAdapter1.Fill(DataSet1);
DataGrid1.DataSource:= DataSet1.Tables[0];
end; // adapter_fill__Click | |
Notez que
- FbDataAdapter.Fill() ne peut être invoqué QUE PAR CODE
- nous aurions pu connecter DataGrid1.DataSource à DataSet1 dans
l'Inspecteur d'Objet, mais nous aurions obtenu, après Fill() la grille hiérarchique présentée ci-dessus. Nous ne pouvons pas relier la DataGrid à une TABLE qui n'existera qu'après l'exécution de Fill() !
4.4 - DataSet en mémoire 4.4.1 - Architecture du DataSet Nous allons nous pencher sur les traitements effectués au niveau du tDataSet:
remplir, modifier, trier etc. Nous pourrions utiliser un tDataSet connecté et rempli par un tDataAdapter, comme nous l'avons fait ci-dessus. Il est plus instructif d'utiliser un tDataSet autonome, que nous construirons et
manipulerons par code uniquement. Mais souvenez-vous que tous les traitements pourraient être réalisés sur un tDataSet rempli depuis des TABLEs du Serveur. Nous sommes donc au niveau du tDataSet:
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.4.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_create_in_memory | |
posez un tButton 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);
// -- manually add the OnChange events
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; // tWinForm | et écrivez le code d'affichage
procedure TWinForm.row_changed(sender: tObject;
e: System.Data.DataRowChangeEventArgs);
// -- e.Action, e.Row begin
display(System.String.Format('on_row_changed {0} ', e.Action)
+ ' : ' + f_display_data_row(e.Row, 2))
end;
procedure TWinForm.column_changed(sender: tObject;
e: System.Data.DataColumnChangeEventArgs); begin
display(System.String.Format('on_column_changed {0} ', e.ProposedValue)
+ ' : ' + f_display_data_row(e.Row, 2))
end; // column_changed | |
Notez que:
- pour éviter l'écriture des événements à la main, nous aurions pu poser un tDataSet sur la Forme, créer ses événements, puis les greffer à notre DataTable
Nous pouvons à présent remplir la DataTable:
- la fonction DataTable.NewRow() créé une nouvelle ligne vierge et retourne une référence vers cette ligne
- utilisant cette référence, nous remplissons les champs
Voici un exemple:
| posez un tButton sur la Forme, nommez-le "insert_", créez sa méthode clic et tapez le code qui créé quelques lignes en remplissant nos deux colonnes:
procedure TWinForm.insert__Click(sender: System.Object;
e: System.EventArgs);
procedure add_row(p_id, p_customer: System.String);
var l_c_data_row: DataRow; begin
l_c_data_row:= g_c_invoice_data_table.NewRow();
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
| | | posez un autre tButton, "display_", et écrivez le code qui affiche le contenu de la DataTable:
function f_display_data_row(p_c_data_row: DataRow;
p_column_count: Integer): System.String;
var l_column_index: Integer; begin
Result:= '';
for l_column_index:= 0 to p_column_count- 1 do
Result:= Result+ ' '+ p_c_data_row.ItemArray[l_column_index].ToString()
end; // f_display_data_row
procedure display_data_table(p_c_data_table: DataTable);
var l_row_index: Integer; begin
for l_row_index:= 0 to p_c_data_table.Rows.Count- 1 do
display(f_display_data_row(p_c_data_table.Rows[l_row_index],
p_c_data_table.Columns.Count));
end; // display_data_table
procedure TWinForm.display__Click(sender: System.Object;
e: System.EventArgs); begin
display_data_table(g_c_invoice_data_table); end; // display__Click |
| | compilez exécutez | |
voilà le résultat: |
Notez que: - nous avons utilisé des boucles pour parcourir les collections de lignes et
de colonnes. Dans les exemples précédents, nous avons utilisé des énumérateurs. Mais comme iCollection comporte une propriété Count, nous pouvons aussi bien utiliser un indice et une boucle
- l'affichage a été effectué en utilisant 2 procédures annexes. En fait, nous avons placé ces procédures dans une unité U_ADO_NET_HELPERS qui est contenue dans les .ZIP qui l'utilisent
- les événements de la DataTable apparaîtront assez maigrelets pour les afficionados habitués ou OnBefore / OnAfter de Delphi.
- pour afficher un énuméré, nous pouvons soit utiliser la réflection, ou
demander à System.String.Format de le faire pour nous:
display(Enum.GetName(TypeOf(DataRowAction),
e.Action));
display(System.String.Format('on_row_changed {0} ',
e.Action)); |
Nous pouvons aussi modifier des valeurs de la DataTable, et effacer des valeurs:
g_c_invoice_data_table.Rows[1]['i_customer']:= 'porsche';
g_c_invoice_data_table.Rows[2].Delete(); |
Pour pouvoir éventuellement annuler les modifications (my_data_row.Cancel()), ou conserver l'historique des valeurs originales, nous pouvons modifier de façon temporaire, en encadrant nos modifications d'une ligne par BeginEdit et
EndEdit:
my_c_data_row:= g_c_invoice_data_table.Rows[1]; my_c_data_row.BeginEdit;
my_c_data_row['i_customer']:= 'renault'; my_c_data_row.EndEdit; |
Les lignes modifiées avant EndEdit sont enregistrées avec un marqueur de modification, et nous pouvons éventuellement récupérer la valeur non modifiée désignée par Current, et la valeur modifiée, appelée ici Proposed. Pour
récupérer les deux valeur: - nous pouvons tester my_data_row.HasVerision(DataRowVersion.Proposed)
- nous pouvons récupérer la valeur actuelle ou future en passant le paramètre optionnel de version à la lecture du champ:
if my_c_data_row.HasVersion(DataRowVersion.Proposed)
then my_proposed_column:= my_c_data_row['i_id', DataRowVersion.Current];
| Notez au passage que pour désigner la valeur Current du type énuméré DataRowVersion, nous utilisons la notation type_énuméré.une_valeur_énumérée
En effet, Java n'a pas de constantes, et utilise ce subterfuge. Et comme C# n'est toujours que du Java ...
Voici donc la modification:
| posez un TextBox sur la forme pour pouvoir taper la nouvelle valeur de "i_customer" | |
posez une CheckBox qui permet d'appeler BeginEdit ou non | | posez un autre tButton, "update_", et écrivez le code qui affiche le
contenu de la DataTable. Nous avons aussi ajouté le code pour afficher la version des champs modifiés:
procedure TWinForm.update__Click(sender: System.Object;
e: System.EventArgs);
var l_c_data_row: DataRow;
l_display: String;
l_column_index: Integer; begin
l_c_data_row:= g_c_invoice_data_table.Rows[1];
if begin_edit_.Checked
then begin
l_c_data_row.BeginEdit;
display('DataRow.begin_edit'); end;
l_c_data_row['i_customer']:= update_text_box_.Text;
l_display:= '';
for l_column_index:= 0 to g_c_invoice_data_table.Columns.Count- 1 do
begin
l_display:= 'col '+ l_column_index.ToString;
if l_c_data_row.HasVersion(DataRowVersion.Current)
then l_display:= l_display+ ': [current]= '
+ l_c_data_row[l_column_index, DataRowVersion.Current].ToString;
if l_c_data_row.HasVersion(DataRowVersion.Proposed)
then l_display:= l_display+ ': [proposed]= '
+ l_c_data_row[l_column_index, DataRowVersion.Proposed].ToString;
display(l_display); end;
end; // update__Click | | |
posez un autre tButton, "display_all", et écrivez le code qui affiche le contenu de la DataTable et l'état de la ligne:
function f_display_data_row_state_version(p_c_data_row: DataRow;
p_column_count: Integer): System.String;
var l_column_index: Integer;
l_current_value, l_proposed_value, l_value: System.String;
begin
Result:= System.String.Format('{0} ', p_c_data_row.RowState);
for l_column_index:= 0 to p_column_count- 1 do
begin
if p_c_data_row.HasVersion(DataRowVersion.Current)
then l_current_value:= p_c_data_row[l_column_index,
DataRowVersion.Current].ToString()
else l_current_value:= '';
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
procedure TWinForm.display_all__Click(sender: System.Object;
e: System.EventArgs);
var l_c_row_enumerator: iEnumerator;
l_c_data_row: DataRow; begin
l_c_row_enumerator:= g_c_invoice_data_table.Rows.GetEnumerator();
display_line;
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
| | | compilez exécutez | |
voilà le résultat: |
4.4.3 - Les Vues Les vues sont des représentations mémoire de la table comportant des
modifications telles que le tri, le filtrage etc. ADO.Net utilise donc un composant séparé pour effectuer les traitements sur les données brutes d'une DataTable. Et chaque DataSet comporte une collection de telles vues:
A chaque table est attachée une vue par défaut, que nous pouvons récupérer en utilisant la propriété ma_table.DefaultView.
my_c_data_view:= my_c_data_table.DefaultView; |
Nous pouvons supprimer des lignes de cette vue en écrivant une expression de filtrage dans la propriété RowFilter:
my_c_data_table.DefaultView.RowFilter:= ' i_customer> ''g'' '; |
Voici le code: | créez un nouveau projet, et appelez-le p_42_data_view | |
posez un tButton sur la Forme, nommez-le "create_data_table_", créez sa méthode clic et tapez le code qui créera une DataSet et le remplira (comme dans l'exemple ci-dessus). Nous avons utilisé une table à 3 colonnes
(i_id, i_customer et i_amount), et l'avons rempli avec:
add_invoice(201, 'apple', 1234.51); add_invoice(202, 'exxon', 625.51);
add_invoice(203, 'dow', 334.51);
add_invoice(204, 'ibm', 134.51); | | |
posez un tButton sur la Forme, nommez-le "filter_", créez sa méthode clic et ajoutez une expression de filtrage à la vue par défaut:
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
| Nous avons, naturellement, écrit un procédure à laquelle nous passons un DataView et qui affiche son contenu. Elle est contenu dans le .ZIP |
| compilez, exécutez, cliquez "create" et "filter" | | voici le résultat
|
Nous pouvons de même trier une DataView en invoquant
my_c_data_view.Sort:= 'i_customer ASC'; |
Et voici le code: | posez un tButton sur la Forme, nommez-le "sort_", créez sa méthode clic
et ajoutez une expression de tri à la vue par défaut:
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
| | | compilez, exécutez, créez la DataTable, et cliquez "sort_" |
| voici le résultat: |
Note que
- si vous exécutez les deux traitements dans la foulées, il faut recliquer "create_" pour avoir une table de plusieurs lignes à trier. En effet, nos DataView sont des pointeurs vers la DefaultDataView, et si nous la
filtrons nous récupérons une vue d'une ligne (dans notre cas) et il n'y a plus grand chose à trier.
J'ai apperçu des Clone() dans le Help, mais n'ai pas approfondi. Si j'ai
bien fait mon job, je crois que vous avez à présent assez de bouteille en ADO.Net pour chercher et trouver comment cloner la vue de départ. Pour singer les pédants "nous laissons en exercice etc etc".
Encore une fonction utile sur les vues: la recherche. Elle est effectuée par la fonction Find() et retourne la ligne correspondant à la recherche:
my_c_table.DefaultView.Sort:= 'i_customer ASC';
my_find_index:= y_c_table.DefaultView.Find('ibm'); |
Soulignons que: - il est OBLIGATOIRE de trier avant d'effectuer la recherche. Une exception est d'ailleurs provoquée si ce n'est pas le cas
- si vous ne souhaitez pas perturber l'affichage, utilisez une table annexe,
puis positionnez vous à la ligne trouvée
Donc: - posez une TextBox sur la Forme
- posez un tButton sur la Forme, nommez-le "sort_", créez sa méthode clic
et ajoutez une expression de tri et recherche dans la vue par défaut:
procedure TWinForm.find__Click(sender: System.Object;
e: System.EventArgs);
var l_c_sorted_data_view: DataView;
l_string_to_find: System.String;
l_find_index: Integer; begin
l_c_sorted_data_view:= g_c_data_set.Tables[0].DefaultView;
l_string_to_find:= find_text_box_.Text; // -- MUST sort
l_c_sorted_data_view.Sort:= 'i_customer ASC';
l_find_index:= l_c_sorted_data_view.Find(l_string_to_find);
display_data_view(l_c_sorted_data_view, 3);
display('found at '+ l_find_index.ToString);
end; // find__Click | - compilez, exécutez, créez la DataTable, tapez une valeur à chercher et cliquez "find_"
- voici le résultat:
Il existe aussi la possibilité de lancer des requêtes SQL sur la DataSet
mémoire (agrégats, jointure), mais nous ne présenterons pas ces traitements ici.
4.5 - Les DataBindings 4.5.1 - Principe des DataBindings
Lorsque nous relions des contrôles visuels à nos données d'un DataSet, nous les connectons toujours à une DataView. En fait, lorsque nous écrivons:
my_data_grid.DataSource:= my_data_table; |
c'est la mécanique polymorphique de Ado.Net qui se met en route pour traduire cela en:
my_data_grid.DataSource:= my_data_table.DefaultDataView; |
Tout se passe bien pour l'affichage, mais les modifications dans les contrôles visuels ne sont pas transféreés dans les données en mémoire. Pour cela, il faut utiliser des DataBindings qui assurent les communications entre les contrôles
visuels et les données des DataSets: Ces DataBindings sont assez délicats à manier, et j'ai trouvé sur Google
plusieurs messages qui étaient beaucoup plus sévères que moi à leur encontre. Le but fondamental des DataBindings est de pouvoir définir des "zones de déplacement synchrones", pour lesquelles tous les contrôles reliés à une zone
notifient et sont notifiés des déplacements dans le DataView sous-jacent. Les DataBindings sont obtenus via des BindingContexts. Ces contextes sont associés au CONTROLES VISUELS. Il y a ainsi un BindingContext associé à la
Forme, ainsi qu'à des DataGrids, des TextBox etc. Nous utiliserons celui de la Forme pour rester simple. Un BindingContext nous permet de récupérer un CurrencyManager, où "currency" signifie "courant".
Le schémma MSDN classique est le suivant: Et ces DataBindings peuvent être utiliser pour synchroniser
- de nombreux contrôles tels que les DataGrids, ListBox, TextBox
- avec de nombreuses sources de données: DataView, bien sûr, mais aussi avec des données classiques (un Integer, un ARRAY OF ...).
Les DataBindings sont de deux types: - ceux qui synchronisent des données simples (un Integer)
- ceux qui synchronisent des listes de données (les valeurs d'une colonne d'un
DataTable, les données d'un ARRAY, d'une Collection). En fait tout ce qui implémente l'interface iList.
4.5.2 - DataBindings simples d'un DataTable
Pour ajouter une liaison à un contrôle visuel, nous appelons simplement
my_visual_control.DataBindings.Add(my_property, my_view, my_expression); | où - my_property indique quelle partie visuelle est liée. Par exemple 'Text'
pour une TextBox
- my_view spécifie d'où viennent les données: par exemple un DataSet ou un ARRAY
- my_expression précise quelle partie de la source est visualisée. Par
exemple la colonne 'customer' d'un DataSet
Une vois la liaison établie, nous créons en général un CurrencyManager qui a la propriété fondamentale Position. En modifiant la valeur de Position (par
code ou par boutons), nous nous déplacerons dans nos données.
Voici un premier exemple avec deux TextBox et une DataTable |
créez un nouveau projet, et appelez-le p_43_simple_data_binding | | déclarez un DataSet dans la partie PUBLIC de la Forme
type TWinForm =
class(System.Windows.Forms.Form)
// ... public
g_c_data_set: DataSet;
constructor Create;
end; // TWinForm | | |
posez un tButton sur la Forme, nommez-le "create_data_table_", créez sa méthode clic et tapez le code qui créera une DataSet
procedure TWinForm.create_data_table__Click(sender: System.Object;
e: System.EventArgs);
var l_c_invoice_data_table: DataTable;
procedure create_column_definitions; // ...
procedure 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;
end; // create_data_set__Click | et remplissez le DataTable (comme dans l'exemple ci-dessus). Nous avons
utilisé une table à 3 colonnes (i_id, i_customer et i_amount), et l'avons rempli avec les mêmes valeurs que ci-dessus | |
posez une TextBox pour afficher la colonne 'i_id' et appelez-le id_text_box | |
posez une TextBox pour afficher la colonne 'i_customer' et appelez-le customer_text_box | |
posez un tButton sur la Forme, nommez-le "bind_data_table_", créez sa méthode clic et ajoutez le code qui va ajouter un DataBinding à chaque Contrôle et créera le CurrencyManager :
var g_c_currency_manager: BindingManagerBase;
procedure TWinForm.bind_data_table__Click(sender: System.Object;
e: System.EventArgs);
var l_c_binding: Binding; begin
l_c_binding:= Binding.Create('Text',
g_c_data_set, 'INVOICE.I_ID');
id_text_box_.DataBindings.Add(l_c_binding);
customer_text_box_.DataBindings.Add('Text',
g_c_data_set, 'invoice.i_customer');
g_c_currency_manager:= BindingContext[g_c_data_set.Tables[0].DefaultView]
as CurrencyManager;
Include(g_c_currency_manager.CurrentChanged, current_changed);
Include(g_c_currency_manager.PositionChanged, position_changed);
end; // bind_data_table__Click | | |
posez un tButton sur la Forme, nommez-le "next_", créez sa méthode clic et modifiez la position du CurrencyManager:
procedure TWinForm.next__Click(sender: System.Object;
e: System.EventArgs); begin
BindingContext[g_c_data_set, 'invoice'].Position:=
BindingContext[g_c_data_set, 'invoice'].Position+ 1;
end; // next__Click | | |
faites de même avec "previous_" | | compilez, exécutez, créez la DataSet, éventuellement affichez son contenu, cliquez "bind_", puis "next_" |
| voici le résultat |
Notez que:
- la liaison est effectuée en précisant "INVOICE.I_ID" : la DataTable et la colonne
- les noms sont en MAJUSCULE. Nous avions déjà rencontré le problème avec Interbase:
- le Dialecte 3 est sensible à la casse
- nous avons créé nos tables avec des noms de table et de colonne et minuscules
- mais quelqu'un (Delphi ? ADO.Net ?) s'amuse à passer en majuscule
- par la suite nous devons utiliser des majuscules ...
Faisons de même avec un ARRAY. Les tableaux dynamiques implémentent bien en .Net l'interface iList. Nous sommes donc dans le cas:
Nous allons créer un tableau d'objets facture: - voici la définition de la CLASS
type c_invoice= class Public
m_id: Integer;
m_customer: System.String;
m_amount: Double;
Constructor create_invoice(p_id: Integer;
p_customer: System.String; p_amount: Double);
procedure display_invoice;
property id : Integer read m_id write m_id;
property customer : System.String
read m_customer write m_customer;
property amount : Double
read m_amount write m_amount;
end; // c_invoice | - le tableau est défini par:
g_c_invoice_array: array of c_invoice; |
Et voici le code: | créez une UNIT u_c_invoice, définissez-y la CLASS c_invoice et
écrivez son CONSTRUCTOR et display_invoice | | importez cette unité dans le USES de l'unité principale |
| déclarez le tableau dans la partie PUBLIC de la Forme | |
posez un tButton sur la Forme, nommez-le "create_array_", créez sa méthode clic et tapez le code qui créera le tableau et le remplira:
procedure TWinForm.create_array__Click(sender: System.Object;
e: System.EventArgs);
var l_invoice_index: Integer;
procedure add_invoice(p_id: Integer;
p_customer: System.String; p_amount: Double);
var l_c_invoice: c_invoice; begin
l_c_invoice:= c_invoice.create_invoice(p_id, p_customer, p_amount);
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 | | |
posez une TextBox pour afficher la PROPERTY id et appelez-la array_id_text_box | |
posez une TextBox pour afficher la PROPERTY customer et appelez-la array_customer_text_box | |
posez un tButton sur la Forme, nommez-le "bind_array_", créez sa méthode clic et ajoutez le code qui va ajouter un DataBinding à chaque Contrôle et créera le CurrencyManager :
procedure TWinForm.bind_array__Click(sender: System.Object;
e: System.EventArgs); begin
array_id_text_box_.DataBindings.Add('Text',
g_c_invoice_array, 'id');
array_customer_text_box_.DataBindings.Add('Text',
g_c_invoice_array, 'customer');
end; // bind_array__Click | | |
posez un tButton sur la Forme, nommez-le "next_t_", créez sa méthode clic et modifiez la position du CurrencyManager:
procedure TWinForm.next_t__Click(sender: System.Object;
e: System.EventArgs); begin
BindingContext[g_c_invoice_array].Position:=
BindingContext[g_c_invoice_array].Position+ 1;
end; // next_t__Click | | |
faites de même avec "previous_t_" | | compilez, exécutez, créez le tableau,, éventuellement affichez son contenu, cliquez "bind_", puis "next_" |
| voici le résultat |
Quelques remarques:
- nous avons placé les déclarations des données dans la Forme. J'imagine que cela facilite les communications avec le ContextManager
- le CurrencyManager n'est utile que pour avancer dans les données par la
manipulation de Position
- pour le tableau, les données qui sont affichées doivent être dans des parties PUBLIC de c_invoice, et nous les avons même placées sous formes de PROPERTYes
- le résultat précédent n'est guère impressionnant. 8 heures de bataille pour trouver les bons composants à connecter. Comme .Net est "hyper articulé" (morcellement de chaque objet) et "hyper polymorphique" (entre "()" ou "[]"
vous pouvez placez quasiment n'importe quoi, le compilateur accepte, et l'exécution vous demande de rallumer le PC), les tâtonnements ont été numbreux.
Ce qui nous a sorti de l'ornière ce sont les événements CurrentChanged et
PositionChanged avec test pas à pas par AS de à qui nous avions affaire dans les paramètres de l'événement. Vous trouverez le code des événements dans les .ZIP.
Alors est-ce que les DataBindings valent tous ces efforts ? A mon sens oui, car ils sont le point obligé pour la synchronisation entre les contrôles visuels et les données (mémoire, puis sur le Serveur).
4.5.3 - DataBinding multiple Lorsque nous souhaitons afficher des listes de données, la liaison est un peu plus compliquée. Il faut spécifier: - la DataSource
- le DisplayMember qui est la valeur à afficher
- le ValueMember qui est la partie utilisée pour syncrhoniser la partie visuelle et la partie mémoire
Voici le projet: |
créez un nouveau projet, et appelez-le p_44_complex_data_binding | | créez le DataSet comme ci-dessus (code identique);
- déclarez un DataSet dans la partie PUBLIC de la Forme
- posez un "create_data_table_", créez le DataSet et remplissez-le
|
| posez une ListBox pour afficher la colonne 'i_customer' et appelez-le data_table_list_box | |
posez une DataGrid et appelez-la data_table_data_grid_ | | posez un tButton sur la Forme, nommez-le "bind_data_table_", créez sa
méthode clic et ajoutez le code qui va ajouter un DataBinding à chaque Contrôle et créera le CurrencyManager :
procedure TWinForm.bind_data_table__Click(sender: System.Object;
e: System.EventArgs); begin
data_table_list_box_.DataSource:=
g_c_data_set.Tables[0].DefaultView;
data_table_list_box_.DisplayMember:= 'i_customer';
data_table_list_box_.ValueMember:= 'i_id';
data_table_data_grid_.DataSource:=
g_c_data_set.Tables[0].DefaultView;
g_c_currency_manager:= BindingContext[g_c_data_set.Tables[0].DefaultView]
as CurrencyManager;
end; // bind_data_table__Click | | |
posez un tButton sur la Forme, nommez-le "next_", créez sa méthode clic et modifiez la position du CurrencyManager: | |
faites de même avec "previous_" | | compilez, exécutez, créez la DataSet, éventuellement affichez son contenu, cliquez "bind_", puis "next_" |
| voici le résultat |
Et voici le traitement our un tableau de c_invoice: | créez le tableau comme dans le projet précédent |
| posez une ListBox pour afficher la colonne 'i_customer' et appelez-le array_list_box | |
posez une DataGrid et appelez-la array_data_grid_ | | posez un tButton sur la Forme, nommez-le "bind_array_", créez sa méthode
clic et ajoutez le code qui va ajouter un DataBinding à chaque Contrôle et créera le CurrencyManager :
procedure TWinForm.bind_array__Click(sender: System.Object;
e: System.EventArgs);
procedure bind_to_data_grid;
var l_c_data_grid_table_style: DataGridTableStyle;
l_c_id_data_grid_text_box_column: DataGridTextBoxColumn;
l_c_customer_data_grid_text_box_column: DataGridTextBoxColumn;
l_c_amount_data_grid_text_box_column: DataGridTextBoxColumn; begin
array_data_grid_.DataSource:= g_c_invoice_array;
// -- cf c# help example : DataGridTableStyle.MappingName Property
l_c_data_grid_table_style:= DataGridTableStyle.Create;
l_c_data_grid_table_style.MappingName:= 'c_invoice[]';
l_c_id_data_grid_text_box_column:= DataGridTextBoxColumn.Create;
l_c_id_data_grid_text_box_column.MappingName:= 'id';
l_c_id_data_grid_text_box_column.HeaderText:= 'id';
l_c_customer_data_grid_text_box_column:= DataGridTextBoxColumn.Create;
l_c_customer_data_grid_text_box_column.MappingName:= 'customer';
l_c_customer_data_grid_text_box_column.HeaderText:= 'customer';
l_c_amount_data_grid_text_box_column:= DatagridTextBoxColumn.Create;
l_c_amount_data_grid_text_box_column.MappingName:= 'amount';
l_c_amount_data_grid_text_box_column.HeaderText:= 'amount';
l_c_data_grid_table_style.GridColumnStyles
.Add(l_c_id_data_grid_text_box_column);
l_c_data_grid_table_style.GridColumnStyles
.Add(l_c_customer_data_grid_text_box_column);
l_c_data_grid_table_style.GridColumnStyles
.Add(l_c_amount_data_grid_text_box_column);
array_data_grid_.TableStyles.Clear();
array_data_grid_.TableStyles.Add(l_c_data_grid_table_style);
end; // bind_to_data_grid begin // bind_array__Click
array_list_box_.DataSource:= g_c_invoice_array;
array_list_box_.DisplayMember:= 'customer';
array_list_box_.ValueMember:= 'id'; bind_to_data_grid;
end; // bind_array__Click | | |
posez un tButton sur la Forme, nommez-le "next_", créez sa méthode clic et modifiez la position du CurrencyManager: | |
faites de même avec "previous_" | | compilez, exécutez, créez la DataSet, éventuellement affichez son contenu, cliquez "bind_", puis "next_" |
| voici le résultat |
4.6 - Modification de données du Serveur 4.6.1 - Principe de Update Pour modifier des données du Serveur, nous procédons en deux étapes
- modification des données de la DataTable en mémoire
- appel de DataAdapter.Update
Lorsque DataAdapter.Update est invoqué, des instruction SQL qui ajoutent,
modifient ou effacent des données sont exécutées. Ces instructions proviennent de deux origines - soit le DataAdapter se débrouille pour déduire les INSERT, UPDATE et
DELETE de la requête SELECT qui a été utilisée pour charger le DataTable
- soit nous écrivons les instructions nous-même
4.6.2 - Modification par code
Pour modifier les données par code uniquement, voici comment procéder: | créez un nouveau projet, et appelez-le p_51_update_dataset_code |
| posez un tButton sur la Forme, nommez-le "change_training_", créez sa méthode clic et tapez le code qui va créer un DataSet, le charger depuis
le Serveur, et effectuer quelques modification des données chargées du Serveur:
procedure TWinForm.change_training__Click(sender: System.Object;
e: System.EventArgs); // -- insert, delete, update
var l_c_connection: FbConnection;
l_c_training_data_table: DataTable;
l_c_data_row: DataRow;
l_offset: System.String; begin
l_c_connection:= FbConnection.Create(k_connection_string);
g_c_data_adapter:= FbDataAdapter.Create(k_select_training,
l_c_connection);
g_c_data_set:= DataSet.Create('my_dataset');
g_c_data_adapter.TableMappings.Add('Table', 'my_training');
g_c_data_adapter.Fill(g_c_data_set);
l_c_training_data_table:= g_c_data_set.Tables['my_training'];
l_offset:= offset_text_box_.Text;
// -- insert a row
l_c_data_row:= l_c_training_data_table.NewRow();
l_c_data_row['t_id']:= 555+ Convert.ToInt32(l_offset);
l_c_data_row['t_name']:= 'Sql Server Ado.Net';
l_c_data_row['t_days']:= 3; l_c_data_row['t_price']:= 1400;
l_c_training_data_table.Rows.Add(l_c_data_row);
// -- modify a row
l_c_data_row:= l_c_training_data_table.Rows[3];
l_c_data_row['t_price']:= System.Object('2350');
end; // change_training__Click | |
Pour utiliser la modification automatique, il suffit d'appeler
DataAdapter.Update(). Mais pour connaître le SQL utilisé par le DataAdapter, nous utilisons un CommandBuilder, et lui demandons d'afficher les commandes qui ont été synthétisée par le DataAdapter.
Cela se passe ainsi: | posez un tButton sur la Forme, nommez-le "update_command_builder_" et tapez le code suivant dans son clic:
procedure TWinForm.update_commandbuilder__Click(sender: System.Object;
e: System.EventArgs);
var l_c_command_builder: FbCommandBuilder; begin
l_c_command_builder:= FbCommandBuilder.Create(g_c_data_adapter);
g_c_data_adapter.Update(g_c_data_set);
display(l_c_command_builder.GetUpdateCommand.CommandText);
end; // update_commandbuilder__Click | | |
compilez et exécutez. Cliquez "change_training_" et "update_commandbuilder_" | | voici l'affichage de la requête de modification:
|
La requête est plutôt complexe. Aussi nous l'avons indentée un peu en utilisant notre éditeur HTML de SQL, dont le résultat est le suivant:
UPDATE "TRAINING"
SET "T_ID" = @p1,
"T_NAME" = @p2, "T_DAYS" = @p3,
"T_PRICE" = @p4 WHERE (
("T_ID" = @p5)
AND (
("T_NAME" IS NULL
AND
COALESCE(@p6, CAST(NULL AS CHAR(24))) IS NULL)
OR ("T_NAME" = @p6) )
AND (
( "T_DAYS" IS NULL
AND
COALESCE(@p7, CAST(NULL AS INTEGER)) IS NULL)
OR
("T_DAYS" = @p7) )
AND (
( "T_PRICE" IS NULL
AND
COALESCE(@p8, CAST(NULL AS NUMERIC(5,2))) IS NULL)
OR ("T_PRICE" = @p8))
) | Sans rentrer en détail, il s'agit de tester la valeur précédente d'un enregistrement en évitant de comparer à des valeurs NULL.
4.6.3 - Ecriture de SQL manuel Si nous savons que seules certaines colonnes ont été modifiées, il est plus efficace de générer notre propre SQL. Le principe est le suivant:
- nous examinons chaque ligne de la DataTable, et testons sa valeur State: si elle a été modifiée, nous appelons une procédure qui va exécuter la requête SQL adaptée
- pour INSERT, c'est très simple, nous remplissons simplement VALUES avec les valeur de la ligne
- pour UPDATE nous butons, comme toujours, sur le problème des accès concurrents:
- nous devons dans le WHERE indiquer quelle ligne du Serveur nous souhaitons modifier
- le DataSet mémoire contient les valeur originales, et nous trouvons dans
les valeurs originales la valeur que la clé de la ligne avait au moment du SELECT
- nous pouvons aussi, si le traitement l'exige, vérifier que d'autres valeurs n'ont pas changé. Pour une réservation d'avion la clé (le numéro
du vol) est utilisée, mais il est vraissemblable que nous nous assurons avant de réserver des places qu'un autre voyagiste n'a pas raflé toutes les places entre le moment ou nous avons lu par SELECT les informations
sur le vol et le moment ou le client se décide à placer sa commande. Notre UPDATE incluera donc dans WHERE la comparaison du nombre de places.
Voici donc comment effectuer la modification par SQL manuel
| posez un tButton sur la Forme, nommez-le "update_manual_sql_" et tapez le code suivant dans son clic:
procedure TWinForm.update_manual_sql__Click(sender: System.Object;
e: System.EventArgs);
var l_c_connection: FbConnection;
procedure do_insert_row(p_c_added_row: DataRow);
// ...
procedure do_update_row(p_c_modified_row: DataRow);
// ... var l_c_row_enumerator: iEnumerator;
l_c_row: DataRow; begin // submit_updates
l_c_connection:= FbConnection.Create(k_connection_string);
l_c_connection.Open();
l_c_row_enumerator:= g_c_data_set.Tables[0].Rows.GetEnumerator();
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 l_c_connection.Close();
end; // update_manual_sql__Click | Pour INSERT nous avons opté pour une construction directe de la requête:
procedure do_insert_row(p_c_added_row: DataRow);
var l_values, l_request: System.String;
l_c_command: FbCommand; begin
l_values:= p_c_added_row['t_id'].ToString
+ ', '''+ p_c_added_row['t_name'].ToString+ ''''
+ ', '+ p_c_added_row['t_days'].ToString
+ ', '+ f_replace_character(
p_c_added_row['t_price'].ToString, ',', '.');
l_request:= 'INSERT INTO training '
+ ' (t_id, t_name, t_days, t_price) '+ k_new_line
+ ' VALUES ('+ l_values+ ')';
l_c_command:= FbCommand.Create(l_request, l_c_connection);
l_c_command.ExecuteNonQuery; end; // do_insert_row |
Et pour UPDATE nous avons utilisé une requête paramétrée:
procedure do_update_row(p_c_modified_row: DataRow);
const k_update_training= 'UPDATE training '
+ ' SET t_price= @t_price '
+ ' WHERE t_id= @t_id';
var l_c_command: FbCommand; begin
l_c_command:= FbCommand.Create(k_update_training, l_c_connection);
l_c_command.Parameters.Add('@t_id', FbDbType.Integer);
l_c_command.Parameters.Add('@t_price', FbDbType.Float);
l_c_command.Parameters['@t_id'].Value:=
p_c_modified_row['t_id', DataRowVersion.Original];
l_c_command.Parameters['@t_price'].Value:=
p_c_modified_row['t_price'];
l_c_command.ExecuteNonQuery; end; // do_update_row |
| | compilez et exécutez |
4.6.4 - Modification par des composant
Nous pouvons utiliser des composants de la Palette pour effectuer les traitements précédents. Voici le projet:
La modification manuelle est réalisée comme pour la modification via le code.
5 - Télécharger le code source Vous pouvez télécharger:
Ce .ZIP qui comprend: - le .DPR, la forme principale, les formes annexes eventuelles
- les fichiers de paramètres (le schéma et le batch de création)
- dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent: - créez un répertoire n'importe où sur votre machine
- placez le .ZIP dans ce répertoire
- dézippez et les sous-répertoires nécessaires seront créés
- compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.
La notation utilisée est la notation alsacienne qui consiste à préfixer les identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal,
L_ocal, P_arametre, F_unction, C_lasse. Elle est présentée plus en détail dans l'article La
Notation Alsacienne
Comme d'habitude: - nous vous remercions de nous signaler toute erreur, inexactitude ou problème
de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou non) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
6 - L'auteur
John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |