menu
  Home  ==>  articles  ==>  bdd  ==>  firebird  ==>  firebird_ado_net_tutorial   

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
    image

  • 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)
    image

  • le Serveur se met à l'écoute, puis un Client envoie une requête :

    image

  • 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

    image

  • et naturellement il y a en général plusieurs Clients qui utilisent le Serveur:

    image



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

    image

    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:

    image

  • 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:

    image

  • 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:

    image

  • 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)
    image

  • et les contrôles visuels peuvent être alimentés par des données autres que des DataSets, par exemple des tableaux:

    image

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

    image
    • la sauvegarde des modifications effectuées dans les contrôles visuels: c'est l'instruction DataAdapter.Update()

    image


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é

    image

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

image



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:

image



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)

image

   sélectionnez FbConnection

image

et tirez le sur la Forme

   Delphi dépose FbConnection1 dans la zone des composants non visuels:

image

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é

image

   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:

image

   cliquez "Test"

   la connection est établie

image



Notez que:
  • nous avons utilisé le nom de serveur "localhost" et le nom complet du chemin et de la base. Si vous utilisez un serveur placé sur un autre PC, remplacez ce nom par le nom de votre serveur.

  • lorsque nous avons changé de version Delphi ou téléchargé un autre provider, nous avons eu des exception "assembly not found". Il s'avère qu'en effaçant les fichiers

        FirebirdSql.Data.Firebird.dcpil

    de nos répertoire, Delphi résolvait le problème

  • pendant plusieurs jours, nous n'avons pas pu afficher les messages d'exception en clair. Le .Net Framework avait un problème de "resource reader". Je soupçonne un problème de lecture du fichier d'erreur. L'installation du BDP Provider a résolu le problème. Si donc vous avez cette erreur, installez aussi le BDP Provider .


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

image

   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

image

   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_pathk_file_name
          + ';'k_user_password';'k_other;

var g_c_connectionFbConnection;

procedure TWinForm.connect__Click(senderSystem.Object;
    eSystem.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"

image

   Delphi ouvre le dialogue présentant les Assemblies .Net connues

image

   sélectionnez FirebirdSql.Data.Firebird

image

   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:

image

   compilez et exécutez

   voici le résultat

image



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:

image



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:

  • nous plaçons la requête dans un tampon de FbConnection en utilisant les paramètres de CONSTRUCTEUR:

    my_c_command:= FbCommand.Create('CREATE TABLE ...'FbConnection1);

    Ce qui ressemble à:

    image

  • nous expédions la requête vers le Serveur par

    my_c_command.ExecuteNonQuery();

    où "NonQuery" indique qu'il s'agit de modifier des données du Serveur, et non de les lire (ce qui se ferait par SELECT  )

    Soit:

    image

  • le Serveur nous retourne éventuellement un code d'erreur, ou provoque une exception (table existant déjà ...)


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(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_commandFbCommand;
      l_resultInteger;
  begin
    FbConnection1.Open();
    l_c_command:= FbCommand.Create(k_create_training_tableFbConnection1);
    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:

 
DROP TABLE training

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:

image



Notez que :

  • nous évitons comme la peste d'utiliser le même identificateur partout: une table SQL ORDERS, ayant une colonne "orders", que vous stockez dans un DataSet orders ayant un table mapping 'orders'. Ben voyons ...

    Ceci explique pourquoi nous utilisons des préfixes pour les colonnes de nos tables: t_id, t_name etc

  • 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
  • le développeur Interbase averti aura remarqué l'absence de Transactions omniprésente dans le monde Interbase Firebird. C'est simplement qu'ADO .Net en utilise par défaut, et comme nous n'avons pas plusieurs instructions à protéger en groupe, nous avons choisi dans ce tutorial de ne pas utiliser de Transactions explicites


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_idt_namet_dayst_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_idIntegerp_nameSystem.String;
    p_daysIntegerp_priceDouble);
  var l_valuesl_requestSystem.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_.Checkedl_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_executeBoolean;
    p_requestSystem.String);
  var l_c_commandFbCommand;
      l_countInteger;
  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(senderSystem.ObjecteSystem.EventArgs);

  procedure fill_the_training(p_idIntegerp_nameSystem.String;
      p_daysIntegerp_priceDouble);
    var l_valuesl_requestSystem.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_.Checkedl_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(senderSystem.Object;
    eSystem.EventArgs);
  begin
    FbConnection1.Open();

    execute_non_query(do_execute_.Checkedk_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(senderSystem.Object;
    eSystem.EventArgs);
  begin
    FbConnection1.Open();
    execute_non_query(do_execute_.Checkedk_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() :

    image

  • le Serveur calcule un ordre optimal des traitements :

    image

  • 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

    image

  • le serveur exécute le traitement


Les paramètres sont désignés par "@ identificateur". Par exemple:

 
UPDATE training
  SET t_pricet_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_commandFbCommand;

procedure TWinForm.prepare__Click(senderSystem.Object;
    eSystem.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(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_parameterFbParameter;
  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:

image

La requête qui effectue la lecture des lignes est SELECT:

 
SELECT t_idt_namet_dayst_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
    image

  • le Serveur calcule, à partir d'une ou plusieurs TABLES, la table résultat ("Result Dataset") et la renvoie au Client

    image

  • le SqlDataReader effectue les traitements qu'il souhaite, par exemple afficher les données dans un TextBox

    image



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_pathk_file_name
          + ';'k_user_password';'k_other;

      k_select_training'SELECT * FROM training';

procedure TWinForm.display__Click(senderSystem.Object
    eSystem.EventArgs);
  var l_c_connectionFbConnection;
      l_c_commandFbCommand;
      l_c_readeriDataReader;
      l_row_indexInteger;
      l_column_indexInteger;
      l_displayString;
  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:

image



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:

image

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:

image

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_pathk_file_name
          + ';'k_user_password';'k_other;

      k_select_training'SELECT * FROM training';

procedure TWinForm.adapter__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_connectionFbConnection;
      l_c_commandFbCommand;
      l_c_data_adapterFbDataAdapter;
      l_c_data_setDataset;
      l_c_data_table_training_refDataTable;
  begin
    l_c_connection:= FbConnection.Create(k_connection_string);
    l_c_command:= FbCommand.Create(k_select_trainingl_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:

image



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:

    image

    nous cliquons sur "+":

    image

    et enfin nous cliquons sur "Table":

    image

    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

image

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

image

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:

image
   tapez la commande de sélection

 
SELECT * FROM training

   cliquez "Execute" pour vérifier la syntaxe

   l'Editeur affiche bien la TABLE:

image

   cliquez "Accept" pour fermer l'Editeur

   dans l'onglet "Data Components" de la Palette sélectionnez un DataSet

image

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(senderSystem.Object
    eSystem.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:

image

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
image

Pour le moment, nous allons effectuer des traitements des DataTable par code:

image



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_tableDataTable;

procedure TWinForm.create_data_table__Click(senderSystem.ObjecteSystem.EventArgs);
  var l_c_data_columnDataColumn;
  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.RowChangedrow_changed);

    // -- manually add the OnChange events
    Include(g_c_invoice_data_table.RowChangedrow_changed);
    Include(g_c_invoice_data_table.ColumnChangedcolumn_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:

image

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

image

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(sendertObject;
           eSystem.Data.DataRowChangeEventArgs);
       procedure column_changed(sendertObject;
           eSystem.Data.DataColumnChangeEventArgs);
    end// tWinForm

et écrivez le code d'affichage

procedure TWinForm.row_changed(sendertObject;
    eSystem.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(sendertObject;
    eSystem.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(senderSystem.Object;
    eSystem.EventArgs);

  procedure add_row(p_idp_customerSystem.String);
    var l_c_data_rowDataRow;
    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_rowDataRow
    p_column_countInteger): System.String;
  var l_column_indexInteger;
  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_tableDataTable);
  var l_row_indexInteger;
  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(senderSystem.Object;
    eSystem.EventArgs);
  begin
    display_data_table(g_c_invoice_data_table);
  end// display__Click

   compilez exécutez

   voilà le résultat:

image



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(senderSystem.Object
    eSystem.EventArgs);
  var l_c_data_rowDataRow;
      l_displayString;
      l_column_indexInteger;
  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&nbs