menu
  Home  ==>  articles  ==>  bdd  ==>  interbase  ==>  interbase_dbexpress   

Interbase dbExpress - John COLIBRI.


1 - Introduction

Cet article va vous indiquer comment utiliser le Serveur Interbase fourni avec Delphi en utilisant dbExpress. Ce tutorial a plusieurs objectifs:
  • vous présenter intégralement comment réaliser des applications utilisant des bases de données Sql en Delphi. Tous les exemples ont été codés et les sources sont téléchargeables.
  • effectuer toutes les opérations en Delphi, au lieu d'utiliser des outils annexes tels que WISQL ou IbConfig
  • présenter surtout la partie accès aux données, sans entrer dans le menu détail de la partie visuelle (dbGrid, dbEdit)
Nous avons présenté dans le tutorial interbase comment utiliser des bases de données en mode Client Serveur. Dans cet article, nous nous concentrons sur le mode dbExpress. Les deux articles peuvent être lus indépendemment l'un de l'autre. Si vous avez lu le tutorial Client Serveur auparavant, les paragraphes sur l'installation d'Interbase, et le fonctionnement de SQL en tant que langage sont similaires, et vous pouvez les sauter: la partie spécifique à dbExpress commence à partir de la création de la table.

Pour l'utilisaton d'Interbase avec Delphi 8 et le .Net Framework, vous pouvez consulter les articles suivants:

  • Interbase Ibx.Net: le portage sous .Net de la mécanique Ibx (Delphi 8, Ibx.Net). Le moyen le plus simple d'utiliser Interbase et Delphi 8. Contient aussi un comparatif de toutes ces architectures avec les schémas correspondants
  • Interbase dbExpress.Net: le portage sous .Net de la mécanique dbExpress (Delphi 8, dbExpress.Net). L'utilisation des techniques VCL pour Interbase ET pour les autres serveurs (Oracle, Sql Server, MyBase etc)
  • Delphi 8 Ado.Net: sous Windows Forms, en l'absence de couche spécifique à Interbase nous pouvons utiliser une mécanique similaire à Odbc et proche d'Ado sous Win32. Cet article présente ce fonctionnement
  • Interbase Borland Data Provider: la voie royale sous Windows Forms, et pour toutes les base, même pour tous les langages
Mentionnons qu'Interbase peut aussi être utilisé comme moyen de sauvegarde sous ECO, et comme source de données de pages Asp.Net et de Services Web.



Si après ce premier contact vous souhaitez approfondir vos connaissances, nous vous proposons soit des formations ou des prestations de programmation et d'assistance.

Nous nous adressons pour ce tutorial à un programmeur Delphi:

  • ayant une idée élémentaire de Delphi: Palette, Inspecteur, OnClick. Tout le reste sera expliqué
  • n'ayant pas nécessairement de connaissance SQL. Le fonctionnement des requêtes et leur syntaxe sera présenté



2 - Installation

2.1 - Principe

Le logiciel Interbase comporte essentiellement deux parties:
  • le programme Serveur (le moteur), qui en général est placé sur un PC distant
  • le programme Client, qui dialogue avec les programme applicatifs (Delphi ou autre).
Il faut donc installer les deux parties (sur la même machine ou sur des PC séparés).

Interbase peut fonctionner selon deux modes:

  • le mode dit "local", pour lequel le moteur et le programme client sont sur le même PC. La machine n'a pas besoin d'avoir les couches réseau (TCP\IP, Novell ou autre), ni de carte réseau (carte Ethernet ou liaison série). Le programme applicatif communique avec le Client, qui appelle directement les routines du serveur (sans passer par des appels TCP\IP)

  • le mode Client Serveur
    • les communications entre la DLL client et le Serveur passent par les couches réseau de Windows: il faut que ces couches réseau soient installées (même si le Serveur et le Client sont sur le même PC)
    • le programme applicatif devra désigner le serveur en utilisant l'adresse IP du serveur.

Dans les deux cas, l'installation se fait en utilisant l'installateur du CD Delphi (ou du CD Interbase). Nous présenterons l'installation à partir du CD Delphi.



2.2 - Installation avec Delphi

Lors de l'installation de Delphi, Installshield propose d'installer aussi Interbase.

Vous pouvez répondre oui et Interbase sera installé

Si vous avez répondu "Non", voici comment procéder:



2.3 - Installation du Mode Local

Pour installer Interbase en mode local:
   insérez le CD Delphi
   sélectionnez "Interbase Desktop Edition"
   répondez oui à toutes les questions
   vous pouvez vérifier que
  • le répertoire "c:\Program Files\Borland\" contient bien un sous-répertoire "Interbase"
  • le répertoire "c:\Windows\SYSTEM\" contient bien la DLL client "gds32.dll"
  • le menu démarrer propose à présent une option "Interbase" avec "Interbase Server Manager". Cliquez sur ce choix, et le gestionnaire du serveur apparaît:




2.4 - Installation du Mode Distant

Installez le Serveur sur le PC serveur, en procédant comme ci-dessus, mais sélectionnez "Interbase 6 Server". La partie Client installée sur le PC du Serveur sera utilisée pour les logiciels de gestion du Serveur

Installez le Client sur le PC client:
   sélectionnez "Interbase 6 Server"
   après les dialogues de copyright ou autres apparaîtra:

   sélectionnez "Interbase Client"


2.5 - Suppression d'Interbase

Pour supprimer Interbase:
   ouvrez le Serveur Manager et cliquez "Stop" (pour arrêter le moteur)
   ouvrez le panneau de configuration, sélectionnez "Ajout / Suppression de logiciel", sélectionnez "Interbase" et cliquez Oui partout



3 - Créer la Base

3.1 - SQL et la création de bases

Pour créer une base, il faut, pour tous les moteurs, utiliser des outils spéciaux, le langage SQL ne contenant pas de requête spécifique pour cette opération

Pour Interbase, ce sont des primitives de l'API native du moteur qui permettent cette création. Elle n'est pas possible depuis les composants générique d'accès aux données (tTable, tQuery). En revanche, Jeff Overcash a ajouté à tIbDatabase la méthode qui utilise les API Interbase natifs et permet la création de la base.

Les éléments à fournir sont les suivants;

  • le chemin DOS où sera placé le fichier .GDB
  • le dialecte Interbase à utiliser. En gros, le dialecte détermine le traitement de certains types de données Interbase (en dialecte 3 nous pouvons utiliser des Integer 64 bits et des tTimeStamps, minuscule ou majuscule sont reconnus etc). Nous choisirons le dialecte 3 (le plus récent).
  • le nom d'utilisateur et le mot de passe du Serveur. Ce sont les noms du serveur par défaut (la base n'étant pas encore créée, elle)
  • des paramètres d'optimisation, tels que la taille des pages du cache
Plus précisément:
  • le chemin est placé dans tIbDatabase.DataBaseName
  • le dialecte est placé dans tIbDatabase.SqlDialect
  • le nom d'utilisateur et le mot de passe, ainsi que les autres paramètres sont placés dans la propriété fourre-tout Params
  • la méthode de création est simplement:
          tIbDatabase.CreateDataBase



3.2 - Création de la base

Nous allons créer une base contenant les stages offerts par l'Institut Pascal. Nous placerons cette base dans le répertoire "..\data\" (situé au même niveau que le .EXE. Le .ZIP téléchargeable créera ce répertoire automatiquement). Le nom du fichier sera "Institut_Pascal.GDB".

Pour créer la base:
   créez une nouvelle application et appelez-la "p_ib_create_base"
   sélectionnez dans la page "Interbase" de la Palette le composant tIbTransaction:

et posez ce composant sur la tForm

   sélectionnez de même une tIbDatabase

et placez-la sur la tForm

   initialisez IbDatabase.DefaultTransaction vers IbTransaction1
   utilisez un tButton pour créer la base:
  • placez un tButton, nommez-le create_base_, et créez sa méthode OnClick
  • voici le code de cette méthode:

        procedure TForm1.create_database_Click(SenderTObject);
          begin
            with IbDatabase1 do
            begin
              DatabaseName:= '..\data\Institut_Pascal.gdb';
              SqlDialect:= 3;

              Params.Add('USER "SYSDBA"');
              Params.Add('PASSWORD "masterkey"');
              Params.Add('PAGE_SIZE 4096');

              CreateDatabase;
            end// with IbDataBase1
          end// create_database_Click


   compilez et exécutez
   cliquez Button1
   le répertoire ..\data\ contient bien le fichier "Institut_Pascal.GDB" dont la taille est d'environ 580 K
Notez que:

  • si le fichier formation.gdb existait déjà il faut tester sa présence (FileExists) et l'effacer (Erase)
  • si nous souhaitons répéter la création, il faudrait purger les Params avant tout ajout par Add (Params.Clear)
Vous pouvez télécharger ce projet "ib_dbx_create_base.zip".

Maintenant que notre base est créée, nous allons utiliser les composants dbExpress pour accéder aux données de notre base. Si vous souhaitez continuer à utiliser les composants IbDatabase, IbQuery etc, consultez le tutorial Interbase en mode Client Serveur.


4 - Connection à une base

Pour nous connecter à une base Interbase nous allons utiliser un composant SqlConnection.

Ce composant doit être initialisé avec:

  • dans DriverName, le nom du pilote Interbase:
           Interbase
  • dans ConnectionName le nom de la connexion:
           IbLocal
  • dans Params les paramètres propres à Interbase, tels que le nom d'utilisateur, le mot de passe, le chemin où se trouve le fichier .GDB etc
Pour spécifier l'endroit où se trouve le fichier .GDB, nous fournissons:
  • soit le chemin, si nous travaillons en local:
            c:\programs\interbase\data\Institut_Pascal.gdb
    ou
            ..\data\Institut_Pascal.gdb
  • soit l'URL du PC qui héberge le Serveur:
            127.0.0.1\ c:\programs\interbase\data\Institut_Pascal.gdb
            HostName\ c:\programs\interbase\data\Institut_Pascal.gdb
            www.jcolibri.com\ c:\programs\interbase\data\Institut_Pascal.gdb
Pour initialiser tous ces paramètres, nous employons l'éditeur de tSqlConnection:
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlConnection:

et posez-le sur la tForm

   cliquez deux fois sur SqlConnection1 pour ouvrir l'éditeur de tIbDatabase
   Delphi présente le dialogue suivant:

Le premier pilote est utilisé par défaut (DB2 dans notre cas)
   ouvrez la combo box "DriverName" et sélectionnez "Interbase"
   dans le listbox "ConnectionName" sélectionnez "IbLocal"
   Delphi affiche les paramètres du pilote Interbase
   dans la ligne "Database" de la listbox de droite
  • dans "Database", tapez le nom de la base
          ..\data\Institut_Pascal.gdb
  • dans "Dialect", tapez la valeur 3
  • dans "User Name", tapez
          SYSDBA
  • dans "PassWord", tapez
          masterkey
   le résultat est:

   fermez l'éditeur

   sélectionnez dans l'Inspecteur d'Objet la propriété LoginPrompt et basculez sa valeur sur False
   sélectionnez Connected et basculez sa valeur sur True
   au bout d'un "petit" instant, la valeur bascule sur True


Notez que:
  • lorsque nous avons créé la base, nous avons choisi le "dialecte 3". C'est la version Sql du moteur
  • lorsque nous connectons la base, nous avons choisi le "dialecte 3": c'est le dialecte du client Interbase.
  • la connection est le test IMPERATIF pour vérifier que nous pouvons travailler en Interbase. Nous recommandons d'effectuer cette connection systématiquement avant de poser des tonnes de composants sur la tForm ou d'écrire des milliers de lignes
  • si nous connectons la base en mode conception, Delphi ouvrira aussi la base lorsque l'exécution sera lancée. Ces deux connections sont comptabilisés par le gestionnaire de licences Interbase comme 2 connections séparées (ce sont bien deux processus Windows).

    Si vous travaillez en utilisant la version Delphi d'Interbase, il vaut mieux fermer la connection en mode conception, puis la rouvrir lors de l'exécution. En fait, chaque accès à Interbase ouvrira automatiquement la connexion. Nous pouvons donc:

    • soit laisse dbExpress ouvrir automatiquement la connexion
    • soit ouvrir la connexion lors de l'exécution en appelant;
              tSqlConnection.Connected:= True;
    Nous allons donc fermer la connexion en mode conception:
       sélectionnez SqlConnection1, sélectionnez dans l'Inspecteur d'Objet la propriété Connected et basculez sa valeur sur False

5 - Créer une Table

5.1 - Principe

Nous allons créer une table contenant pour chaque formation:
  • un code (par exemple 8)
  • un nom (par exemple "Delphi Interbase")
  • un prix (par exemple 1.400)
Pour cela nous devons envoyer une requête en langage SQL vers le Serveur Interbase.

La syntaxe de cette requête est:

 CREATE TABLE formations
     (f_numero INTEGER, f_nom CHARACTER(11), f_jours INTEGER, f_prix NUMERIC(5, 2) )

Il suffit donc de choisir un nom de table, et le nom de chaque colonne avec son type.

Parmi les types autorisés par Interbase citons:

  • INTEGER pour les valeurs entières 32 bits
  • SMALLINT pour les valeurs entières 16 bits
  • NUMERIC(decimales, précision) pour une valeur numérique flottante
  • DATE pour une date
  • CHARACTER(taille) pour des caractères
Pour envoyer cette requête vers le Serveur:
  • nous utilisons un tSqlConnection qui assurera la connection vers le Serveur
  • nous utilisons un tSqlQuery
    • nous le relions à tSqlConnection
    • nous plaçons la requête SQL dans sa propriété ISqlQuery.SQL (via l'Inspecteur ou en code)
    • nous appelons tSqlQuery.ExecSql


5.2 - Utilisation de SQL

La requête à envoyer au Serveur est placée dans tSqlQuery.Sql. tSqlQuery.Sql est un descendant de tStrings. Nous pouvons donc utiliser, par exemple:

          SqlQuery1.Sql.Add('CREATE TABLE formations (f_numero INTEGER, f_nom CHARACTER(11))');

Pour construire la requête:

  • nous pouvons utiliser Add pour ajouter une ligne de requête, Text pour affecter une requête, Clear pour purger tout texte antérieur, ou même LoadFromFile pour lire un fichier .txt contenant la requête:
          SqlQuery1.Sql.LoadFromFile('cree_formation.txt');

La mise en page n'a aucune importance pour le Serveur: la requête peut être répartie en plusieurs lignes:

          SqlQuery1.Sql.Add('CREATE TABLE');
          SqlQuery1.Sql.Add('  formations ');
          SqlQuery1.Sql.Add('  (f_numero INTEGER, f_nom CHARACTER(11))');
  • nous pouvons entrer la requête en utilisant l'Inspecteur d'Objet. Si nous cliquons sur Sql, Delphi affiche:

    et vous pouvez taper manuellement la requête dans la partie "SQL"

  • nous pouvons aussi créer la String en plusieurs étapes par toutes les primitives de String telles que la concaténation, Insert, le test par Pos ou autre. Par exemple:
          l_requete:= 'CREATE TABLE 'Edit1.Text' (';
          l_requete:= l_requeteEdit2.Text')';
          SqlQuery1.Sql.Add(l_requete);



5.3 - Comment ça Marche

Au niveau fonctionnement:
  • lorsque nous construisons la requête par SqlQuery.Sql.Add, ce texte de requête est à ce stade une tStrings en mémoire de notre PC.

    C'est une tStrings comme n'importe quelle autre. Ce qui explique qu'aucune vérification de syntaxe n'est effectuée.

  • Lorsque nous exécutons:

                 SqlQuery1.ExecSql;

    alors:

    • la requête est envoyée au Serveur via le Client
    • le Serveur traite la requête et:
      • crée la table si la requête est correcte:

      • retourne une erreur transformée par Delphi en exception en cas de problème
Notez que l'envoi de toute requête qui modifie des données du Serveur (et la création d'une nouvelle table est bien une modification) ne peut se faire que par du code (PAS en basculant SqlQuery1.Active sur True en mode conception)



5.4 - L'application

Pour créer notre table
   créez une nouvelle application et appelez-la "ib_dbx_create_table"

   sélectionnez dans la page "dbExpress" de la Palette le composant SqlConnection:

et posez-le sur la tForm

   Cliquez deux fois sur SqlConnection1, renseignez "Driver Name", "Connection Name", "DataBase", "Dialect", "User Name" et "Pass Word"

Sélectionnez la propriété LoginPrompt et basculez sa valeur sur False

Vérifiez la connection en basculant SqlConnection1.Connected sur True, puis fermez la connection (pour éviter de monopoliser un utilisateur). La connection sera ouverte avant l'envoi de la requête

   sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery:

   sélectionnez sa propriété SqlConnection et initialisez-la à

    SqlConnection1

   placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions de création:

    procedure TForm1.create_table_Click(SenderTObject);
      begin
        with SqlQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('CREATE TABLE formations');
            Add('  ('
                +'    f_numero INTEGER,');
            Add('     f_nom CHAR(23),');
            Add('     f_jours SMALLINT,');
            Add('     f_prix NUMERIC(5, 2)');
            Add('   )');
          end// with Sql

          Try
            ExecSql;
          except
            on eException do
              begin
                display('  *** pb_create 'e.Message);
              end;
          end;
        end// with SqlQuery1
      end// create_table_Click


   compilez, exécutez, et cliquez le bouton


Vous pouvez télécharger le sources du projet "ib_dbx_create_table.zip".



5.5 - Vérifier la création

Nous pouvons vérifier que la création a été effectuée en utilisant l'explorateur de bases de données Delphi, ou en lisant les données ce cette table dans notre application.

Pour lire les données il suffit d'envoyer la requête

SELECT * FROM formations

au moteur. Nous verrons cette requête SELECT en détail plus bas, mais voici comment procéder pour notre test:
   placez un second tButton sur la Forme et placez-y la requête de lecture:

    procedure TForm1.select_Click(SenderTObject);
      begin
        with SQLQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('SELECT * FROM formations');

            Try
              Open;
              display('  ok');
            except
              on eException do
                display('  *** pb 'e.Message);
            end;
          end// with Sql
        end// with SQLQuery1
      end// select_Click


   compilez, exécutez, et cliquez le bouton


5.6 - Effacer une table

Pour supprimer une Table, il faut exécuter la requête:

DROP TABLE formations

Donc:
   placez un autre tButton sur la Forme et placez-y la requête de suppression:

    procedure TForm1.drop_table_Click(SenderTObject);
      begin
        with SQLQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('DROP TABLE formations');

            Try
              ExecSql;
              display('  ok');
            except
              on eException do
                display('  *** pb 'e.Message);
            end;
          end// with Sql
        end// with SQLQuery1
      end// drop_table_Click


   compilez, exécutez, et cliquez le bouton


Notez que:
  • nous avons le même SqlQuery en changeant le contenu de sa propriété SQL. Nous aurions aussi bien pu utiliser 3 SqlQuery séparés.
  • notez que pour envoyer une requête de modification il faut utiliser tSqlQuery.ExecSql, alors que pour lire les données nous utilisons tSqlQuery.Open.

    tSqlQuery.Open est équivalent à tSqlQuery1.Active:= True (mais pas à ExecSql). Comme l'Inspecteur propose Active (pour pouvoir positionner ses éléments visuels), nous pouvons ouvrir une Table depuis l'Inspecteur, mais nous ne pouvons pas créer de Table en mode conception (il faut exécuter du code)



5.7 - Les Transactions

En Interbase, toutes les requêtes sont créées dans le cadre d'une transaction. Les transaction sont un mécanisme qui garantit que plusieurs actions sont réalisées en entier ou annulées. L'archétype est le transfert bancaire: si nous débitons DUPOND pour créditer MARTIN, le système doit garantir que soit les deux modifications sont réalisée ou aucune ne l'est.

Si nous utilisons InterbaseExpress (IBX, qui est le jeu de composants utilisés dans l'article Interbase Tutorial ), il faut systématiquement placer un composant IbTransaction sur la tForm. C'est d'ailleurs ce que nous avons effectué pour la création de la base au début de cet article.

Si nous utilisons dbExpress, c'est Delphi qui se charge de gérer les transactions de façon transparente. Nous n'avons donc pas utilisé de composant transaction pour créer notre table.

Nous pouvons cependant le faire si nous souhaitons regrouper plusieurs actions. Voici un exemple de suppression de Table utilisant une transaction explicite:

    procedure TForm1.drop_with_transaction_Click(SenderTObject);
      var l_transaction_descriptorTTransactionDesc;
      begin
        with SQLQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('DROP TABLE formations');

            if not SQLConnection1.InTransaction
              then begin
                  l_transaction_descriptor.TransactionID:= 1;
                  l_transaction_descriptor.IsolationLevel:= xilREADCOMMITTED;
                  SQLConnection1.StartTransaction(l_transaction_descriptor);

                  Try
                    ExecSql;
                    SQLConnection1.Commit(l_transaction_descriptor);
                    display('  ok');
                  except
                    on eException do
                      begin
                        display('  *** pb 'e.Message);
                        SQLConnection1.Rollback(l_transaction_descriptor);
                      end;
                  end;
                end
              else display('already_in_transaction');
          end// with Sql
        end// with SQLQuery1
      end// drop_with_transaction_Click




5.8 - Automatisation de la création de Table

Si nous avons plusieurs tables à créer, le codage en dur se révèle très vite fastidieux. Or il est très simple de paramétrer une procédure générique, et d'appeler cette procédure en lisant les paramètres depuis un fichier.

Une solution est de placer sur disque les requêtes SQL et de lire et exécuter ces requêtes les unes après les autres.

Nous avons préféré utiliser une définition plus schématique qui est analysée par la procédure de lecture.

Voici notre schéma que nous avons tapé dans NotePad et placé dans le fichier "schema_simple.txt":

formations
      f_numero INTEGER
      f_nom CHAR(23)
      f_jours INTEGER
      f_prix NUMERIC(9, 2)
dates
      d_formation INTEGER
      d_date TIMESTAMP
      d_ville INTEGER
villes
      v_numero INTEGER
      v_nom CHAR(30)

Nous avons simplement fourni

  • le nom de chaque table, à la marge
  • le nom des champs et leur type Interbase, indenté de deux espaces
On peut difficilement faire plus simple !

Notre programme va alors

  • lire le fichier
  • générer la requête pour créer chaque table
Et:
  • la création de la table est réalisée par une classe dont voici la définition:

        USES ... , SqlExpr;

        type c_create_ib_tableclass(c_basic_object)
                                  m_table_nameString;
                                  m_c_ib_dbx_query_reftSqlQuery;
                                  m_c_fieldstStringList;
                                  m_exec_sqlBoolean;

                                  Constructor create_ib_table(p_namep_table_nameString;
                                      p_c_ib_querytSqlQuery);
                                  procedure drop_table;
                                  procedure create_table;
                                  procedure display_sql;
                                  Destructor DestroyOverride;
                                end// c_create_ib_table

  • la méthode de création de la table est la suivante:

        procedure c_create_ib_table.create_table;
          var l_field_indexInteger;
          begin
            drop_table;

            with m_c_ib_dbx_query_refSql do
            begin
              Clear;

              Add('CREATE TABLE 'm_table_name' (');
              for l_field_index:= 0 to m_c_fields.Count- 2 do
                Add(m_c_fields[l_field_index]+ ',');
              Add(m_c_fields[m_c_fields.Count- 1]+ ' )');

              if m_exec_sql
                then
                  try
                    ExecSql;

                    display('  ok 'm_table_name);
                  except
                    on eException do
                       display('  *** pb_create 'e.Message);
                  end // try ... Except
                else display('did_not_ask_to_create');
            end// with m_c_ib_dbx_query_ref, Sql
          end// create_table

  • cette méthode utilise la liste de définition des champs qui est chargée à partir du fichier par la procédure suivante

        procedure TForm1.create_script_Click(SenderTObject);
          const k_exec_sql= [0, 1, 2];
          var l_list_indexInteger;
              l_linel_trimmed_lineString;
              l_c_create_ib_tablec_create_ib_table;
              l_table_indexInteger;
          begin
            with tStringList.Create do
            begin
              LoadFromFile(k_script_pathk_script_name);
              l_c_create_ib_table:= Nil;

              l_table_index:= 0;

              for l_list_index:= 0 to Count- 1 do
              begin
                l_line:= Strings[l_list_index];
                l_trimmed_line:= Trim(l_line);
                if l_trimmed_line<> ''
                  then begin
                      if l_trimmed_linel_line
                        then begin
                            if Assigned(l_c_create_ib_table)
                              then begin
                                  l_c_create_ib_table.create_table;
                                  l_c_create_ib_table.Free;
                                  Inc(l_table_index);
                                end;

                            l_c_create_ib_table:= c_create_ib_table.create_ib_table(''l_lineSqlQuery1);
                            l_c_create_ib_table.m_exec_sql:= l_table_index in k_exec_sql;
                          end
                        else begin
                            l_c_create_ib_table.m_c_fields.Add(l_trimmed_line);
                          end;
                    end;
              end// for l_list_index

              if Assigned(l_c_create_ib_table)
                then begin
                    l_c_create_ib_table.create_table;
                    l_c_create_ib_table.Free;
                  end;

              Free;
            end// with tStringList
          end// create_script_Click


Notez que:
  • la procédure c_create_ib_table.create_table commence par appeler la procédure c_create_ib_table.drop_table. Pour la première création, ceci est inutile, mais si vous créez une seconde fois la base, il faut d'abord effacer la table, sinon le Serveur refuse la création.
  • mais dans le cas de la première création, DROP TABLE provoque aussi une exception. C'est pourquoi cette instruction est placée dans un TRY ... EXCEPT. Cette exception va interrompre l'exécution. Pour poursuivre la création:
    • vous pouvez taper F9 pour passer outre
    • vous pouvez placer Delphi en mode "ne pas s'arrêter en cas d'exceptions" en suprimant la coche de la CheckBox "stop on Delphi Exceptions" située dans:
          Tools | Debugger | Language Exceptions
    C'est le mode préféré lorsque l'on utilise beaucoup de bases de données, compte tenu des nombreuses exceptions provoquées à tous les niveaux (Serveur Sql, pilote, BDE ou dBExpress, Ibx etc).


Vous pouvez télécharger le source du projet "ib_dbx_create_tables_with_script.zip".




6 - Ajouter des Données

6.1 - Ajout simple

Ajoutons un enregistrement pour le stage

    3, Interbase Delphi

L'instruction SQL est:

 INSERT INTO formations
     (f_numero, f_nom)
     VALUES (3, 'Interbase Delphi')

L'ajout d'enregistrement va modifier les données du Serveur, donc nous utiliserons tSqlQuery.ExecSql.

De façon détaillée:
   créez une nouvelle application et nommez-la "ib_dbx_insert_data"
   placez un tSqlConnection sur la Forme. Cliquez deux fois sur SqlConnection1, renseignez "Driver Name", "Connection Name", "DataBase", "Dialect", "User Name" et "Pass Word".
Basculez LoginPrompt sur False, et vérifiez la connection en basculant SqlConnection1.Connected sur True, puis fermez la connection.
   placez un tSqlQuery sur la tForme
   sélectionnez sa propriété SqlConnection et initialisez-la à SqlConnection1
   placez un tButton sur la Forme et créez sa méthode OnClick. Placez-y les instructions d'ajout:

    procedure TForm1.insert_Click(SenderTObject);
      begin
        with SqlQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('INSERT INTO formations');
            Add('  (f_numero, f_nom)');
            Add('  VALUES (3, ''Delphi Iterbase'')');
          end;

          Try
            ExecSql;
            display('  ok');
          except
            on eException do
              display('  *** pb_insert 'e.Message);
          end;
        end// with SqlQuery1
      end// insert_Click

   compilez, exécutez, et cliquez le bouton


Vous pouvez télécharger les sources du projet "ib_dbx_insert_data.zip".



6.2 - Type CHARACTER

Pour spécifier les valeurs CHARACTER, Sql exige que la chaîne soit entourée de guillemets. Suivant les Serveurs, il faut utiliser un guillemet simple ou double:

 VALUES (3, 'Interbase Delphi')

ou

 VALUES (3, "Interbase Delphi")

De plus si notre valeur est nichée dans une String Pascal, il faut dédoubler les guillemets

IbQuery1.Sql.Add('  VALUES (3, ''Interbase Delphi'')');

Pour simplifier cet imbroglio de guillemets, Delphi propose la méthode QuotedStr:

IbQuery1.Sql.Add('  VALUES (3, 'QuotedStr('Interbase')+ ')');



6.3 - Type NUMERIC

Pour les valeurs numériques avec décimales, nous devons batailler avec les points et les virgules:
  • aux US, la partie entière est séparée de la partie décimale par un point:

        3.1415

    alors qu'en Europe nous disons

        3,1415

  • les valeurs que nous tapons en PASCAL doivent donc respecter la syntaxe US

  • mais si nous utilisons des primitives de conversion interface utilisateur <=> Delphi, Delphi lit les paramètres de localisation dans la base de registre Windows. Pour ma machine il est dit que le séparateur décimal est la virgule. Par conséquent les fonctions telles que:

        FloatToStr

    attendent une virgule

    Nous pouvons imposer le séparateur à utiliser en spécifiant:

DecimalSeparator:= '.';

La règle est donc simple:

  • les valeurs Delphi (Double ou autre) utilisent le point '.'
  • les composants visuels (tEdit etc) et les primitives de conversion (FloatToStr) utilisent la virgule ','


6.4 - Type DATE  

Un problème similaire intervient pour les dates:
  • Sql utilise le format US "année mois jour". EXCLUSIVEMENT
  • suivant le type de primitive, Delphi utilisera un format US ou Européanisé (via le panneau de configuration Windows, ou des primitives de formatage)
  • le séparateur doit être un slash / (et non pas un tiret ou autre)

         2004/03/29

  • les dates doivent être entourée de guillemets:

         '2004/03/29'

    car sinon Delphi effectue une division et calcule un Double, assimilé à un tDateTime, et traduit en une date

En supposant que nous souhaitions fournir la date du 29 Mars 2004, nous pouvons utiliser:

 INSERT INTO dates
     (d_numero, d_date)
     VALUES (3, '2004/03/29')

et:

IbQuery1.Sql.Add('INSERT INTO dates');
IbQuery1.Sql.Add('  (d_numero, d_date');
IbQuery1.Sql.Add('  VALUES (3, ''2004/03/29'')');



6.5 - Automatisation de l'Ajout

Les valeurs à insérer ont été figées dans notre code. Nous pouvons automatiser cet ajout
  • soit par des scripts
  • soit par une procédure amplement paramétrée
  • soit en mode interactif.
Nous allons présenter ici l'utilisation d'une procédure.

En fait il n'y a rien de nouveau par rapport à la technique ci-dessus, sauf que

  • la chaîne de la requête est construite en fonction de paramètres de la procédure
  • la procédure appelante envoie les paramètres requis
Voici un exemple de procédure générique d'ajout:

    procedure insert_generic(p_numberIntegerp_nameStringp_daysIntegerp_costDouble);
      begin
        with Form1SqlQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('INSERT INTO formations');
            Add('  (f_numero, f_nom, f_jours, f_prix)');
            DecimalSeparator:= '.';
            Add('  VALUES ('IntToStr(p_number)+ ', 'QuotedStr(p_name)
                + ', 'IntToStr(p_days)+ ', 'FloatToStr(p_cost)+ ')');
            DecimalSeparator:= ',';
          end;

          Try
            ExecSql;
            display('  ok');
          except
            on eException do
              display('  *** pb_insert 'e.Message);
          end;
        end// with SqlQuery1
      end// insert_generic

Et voici un exemple de procédure appelante

    procedure TForm1.insert_batch_Click(SenderTObject);
      begin
        insert_generic(1, 'Initiation Delphi', 3, 1400.40);
        insert_generic(2, 'Bases de Données Delphi', 3, 1400);
        insert_generic(3, 'Interbase Delphi', 3, 1400);
        insert_generic(4, 'Composants Delphi', 3, 1400);
        insert_generic(5, 'UML et Patterns Delphi', 3, 1400);
        insert_generic(4, 'Initiation Pascal', 4, 1900);
      end// insert_automatic_Click

Notez que:

  • la procédure appelante pourrait aussi bien lire ses donnée d'une autre source (un fichier FILE OF, un fichier ASCII ("comma separates values" ou autre), un autre table (Oracle, Sql Serveur ou même une autre table Interbase...)
  • le paramètres p_cost est de type Double:
    • la procédure appelante envoie une valeur littérale avec un point décimal

             insert_generic(2, 'Bases de Données Delphi', 3, 1.400);

    • la procédure appelée utilise FloatToStr, qui attend une virgule décimale. Nous forçons donc temporairement l'utilisation du point:

                  DecimalSeparator:= '.';
                  Add('  VALUES ('IntToStr(p_number)+ ... + FloatToStr(p_cost)+ ')');
                  DecimalSeparator:= ',';

Pour vérifier que nos ajouts ont effectivement réussi, nous avons ajouté à notre application trois boutons qui ouvrent chaque table et comptent simplement le nombre de fiches. Le véritable affichage du contenu de chaque ligne nous allons le réaliser maintenant, et en utilisant un tClientDataSet.




7 - Lire et Afficher

7.1 - Principe de dbExpress

Le lecteur ayant lu le Tutorial Interbase aura constaté que tous les traitements précédents sont quasiment identiques à ceux que nous avions effectués en utilisant les composants IBX:
  • SqlConnection a remplacé IbDatabase
  • SqlQuery a remplacé IbSql
  • les transactions sont gérées automatiquement par dbExpress, au lieu d'utiliser un ibTransaction explicite
Les choses vont réellement changer avec les composants de lecture. Pour mettre en perspective les différences de traitement, nous allons évoquer succintement les architectures possibles.

7.2 - Le mode Sql pur

Si nous utilisons les API Interbase, nous avons fondamentalement deux types d'instructions Sql:
  • les instructions qui modifient les données (création de table, écriture de données, modifications, effacement...)

  • les instructions qui lisent les données. Le Serveur contient des Tables dont le Client souhaite récupérer des données:

    Le programmeur:

    • prépare un tampon de réception

    • envoie la requête "SELECT ..." vers le serveur

    • le Serveur crée une table correspondant à cette requête:

    • le Serveur envoie les enregistrements demandés par paquets vers le Client qui les dépose dans la mémoire qu'il avait préparée:

Rien ne relie les données reçues à des modifications de ces données.



7.3 - Le BDE

Le moteur de base de données Borland (Borland Database Engine) utilise les API, mais les données lues par SELECT sont stockées dans un cache du BDE. Le Client:
  • associe un tTable à une Table du Serveur
  • lorsque le Client ouvre la Table, le BDE envoie une requête SELECT vers le Serveur
  • les données recueillies sont stockées dans des caches gérés par le BDE
  • le Client récupère ces données pour les traiter dans son code, ou les afficher dans des contrôles tels que les dbGrid

L'avantage de ce stockage intermédiaire par le BDE est le suivant:

  • le Client peut effectuer des déplacements bi-directionnels dans ses données
  • si le Client modifie une valeur, le BDE génère automatiquement l'instruction Sql qui sera expédiée vers le Serveur pour modifier la valeur de la Table
Parmi les inconvénients:
  • le BDE gère apparemment plusieurs caches, avec quelques problèmes de synchronisation entre ces divers caches
  • cette gestion automatique est lourde et explique la taille du BDE (aussi causée par la gestion de nombreux moteurs: Interbase, mais aussi dBase, Paradox, Oracle etc)


7.4 - Interbase Express

Par rapport au BDE, Interbase Express simplifie le traitement en en s'occupant que d'un seul type de Serveur: Interbase.

Les composants utilisés sont alors les suivants:

  • IbDatabase associé à IbTransaction s'occupe de la connexion avec le Client Interbase
  • IbQuery traite les requêtes Sql en les envoyant vers le Serveur et en tamponnant les requêtes SELECT
  • le dbGrid usuel se charge de la visualisation et la saisie

7.5 - dbExpress

L'architecture dbExpress va un pas plus loin que IbX en séparant le stockage de l'accès:
  • SqlConnection s'occupe de la connexion avec le Client Interbase
  • SqlQuery traite les requêtes Sql mais ne tamponne pas les données en lecture
  • le données échangées avec le Serveur utilisent:
    • un composent tDataSetProvider qui met les données sous forme de paquets pour normaliser les échanges avec le Client
    • un tClientDataSet qui se charge de tamponner les données
  • le dbGrid usuel se charge de la visualisation et la saisie
Le schéma correspondant est le suivant:

dbExpress possède aussi deux extensions:

  • il est possible de séparer
    • d'une part SqlConnection, SqlQuery et DataSetProvider sur une machine intermédiaire, placée entre le Serveur et les Clients
    • de l'autre les Clients contenant chacun un ClientDataset, et les composants de contrôle visuel

    Il s'agit alors d'une application 3 niveaux, utilisant pour communiquer entre le PC du milieu et les Clients des composants de communications. Nous n'utiliserons pas cette séparation, et laisserons tous les composants sur le poste Client

  • il est possible de travailler sans aucune connexion au Serveur: le ClientDataSet travaille en mémoire pure, en sauvegardant le contenu de son cache dans un fichier du Client (à un format binaire ou XML n'ayant rien à voir avec Interbase). Il s'agit du travail nomade, illustré par ce schéma:

    Ce type de traitement est appelé aussi par Borland "MyBase".



Nous allons nous intéresser dans la suite de cet article aux lectures et modifications de tables Interbase en utilisant le tClientDataSet.



7.6 - Lecture dans un tClientDataSet

Voici notre première application pour afficher les données:
   créez une nouvelle application et nommez-la "ib_dbx_read"
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlConnection et posez-le sur la tForm.
Cliquez deux fois sur SqlConnection1, renseignez
     "Driver Name" -> Interbase
     "Connection Name" -> IbLocal
     "DataBase" -> ..\data\institut_pascal.gdb
     "Dialect" -> 3
     "User Name" -> SYSDBA
     "Pass Word" -> masterkey
Allez dans l'Inspecteur d'Objet:
  • sélectionnez LoginPrompt et basculez sa valeur sur False
  • vérifiez la connection en basculant Connected sur True, puis fermez la connection en basculant Connected sur False.
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery et posez-le sur la tForm.
  • Sélectionnez sa propriété SqlConnection et initialisez-la à SqlConnection1
  • sélectionnez sa propriété SQL, cliquez sur l'ellipse ... pour ouvrir l'éditeur de chaînes de caractères et tapez-y la requête:

         SELECT * FROM formations

  • cliquez sur "OK" pour ferme l'éditeur
   sélectionnez dans la page "Data Access" de la Palette le composant DatasetProvider:

  • Posez-le sur la tForm.
  • dans sa propriété DataSet sélectionnez SqlQuery1
   sélectionnez dans la page "Data Access" de la Palette le composant ClientDataSet:

  • Posez-le sur la tForm.
  • dans sa propriété ProviderName sélectionnez DatasetProvider1
   sélectionnez dans la page "Data Access" de la Palette le composant DataSource:

  • Posez-le sur la tForm.
  • dans sa propriété DataSet sélectionnez ClientDataSet1
   sélectionnez dans la page "Data Controls" de la Palette le composant dbGrid:

  • Posez-le sur la tForm.
  • dans sa propriété DataSource sélectionnez DataSource1
   sélectionnez sur la Forme SqlClientDataSet1, et basculez sa propriété Active à True

   dbExpress envoie la demande d'ouverture à DataSetProvider1, qui la transmet à SqlQuery1, qui connecte la base, recherche les données et les propage par le chemin inverse dans la dbGrid: vous voyez les donnée affichées:




Notez l'ordre de chaînage des composants:

Cet ordre provient des multiplicités possibles:

  • une tSqlConnection peut être reliée à plusieurs tSqlQueries
  • un tSqlQuery peut être relié à plusieurs tDatasetProviders
  • un tDataSetProvider peut alimenter plusieurs tClientDatasets
  • un tClientDataSet peut être relié à plusieurs tDataSource
  • un tDataSource peut être relié à plusieurs composants visuels tels que la tdbGrid

Si Delphi avait proposé au développeur programmeur de relier tSqlConnection à ses tSqlQueries, il aurait fallu prévoir une LISTE de tSqlQueries associés, alors que dans l'ordre inverse, chaque tSqlQuery ne peut avoir qu'une tSqlConnection, donc une seule ligne dans l'Inspecteur d'Objet.



Vous pouvez télécharger le sources du projet "ib_dbx_read.zip".




8 - Modifier des Données

8.1 - Principe

Pour modifier les données en utilisant le tClientDataset, l'utilisateur va travailler en deux étapes:
  • toutes les modifications (ajout, changement, effacement) sont mémorisés par le tClientDataSet
  • lorsqu'il le souhaite, l'utilisateur peut envoyer les modifications vers le Serveur Interbase
L'utilisateur travaille donc dans un premier temps uniquement avec la mémoire du tClientDataSet. Celui-ci garde la trace intégrale de toutes les modifications:
  • si nous modifions un enregistrement, nous aurons 6 fois l'enregistrement en mémoire (l'original et les 5 modifications)
  • les ajouts sont placés dans la mémoire
  • les effacements sont mémorisés comme une nouvelle ligne avec une marque d'effacement.

8.2 - Les Modifications de tClientDataset

Le tClientDataset contient donc une suite de lignes numérotées:
  • les lignes chargées lors de l'ouverture de tClientDataset
  • les lignes ajoutées
  • les versions modifiées de lignes précédentes
  • les lignes effacées
Le dbGrid ne présente quant à lui que la dernière version de chaque ligne.

Pour visualiser les lignes originales et leurs versions successives, nous allons simplement sauvegarder le tClientDataset dans un fichier .XML et réafficher ce fichier .XML. Ce type d'analyse a été présenté dans l'article Affichage ClientDataset XML. Nous nous contenterons ici d'appeler les classes d'analyse et d'affichage.

Par conséquent:
   créez une nouvelle application et nommez-la "ib_dbx_modify"
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlConnection et posez-le sur la tForm.
Cliquez deux fois sur SqlConnection1, renseignez
     "Driver Name" -> Interbase
     "Connection Name" -> IbLocal
     "DataBase" -> ..\\data\\institut_pascal.gdb
     "Dialect" -> 3
     "User Name" -> SYSDBA
     "Pass Word" -> masterkey
Allez dans l'Inspecteur d'Objet:
  • sélectionnez LoginPrompt et basculez sa valeur sur False
  • vérifiez la connection en basculant Connected sur True, puis fermez la connection en basculant Connected sur False.
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery et posez-le sur la tForm.
  • Sélectionnez sa propriété SqlConnection et initialisez-la à SqlConnection1
  • sélectionnez sa propriété SQL, cliquez sur l'ellipse ... pour ouvrir l'éditeur de chaînes de caractères et tapez-y la requête:
         SELECT * FROM FORMATIONS
  • cliquez sur "OK" pour ferme l'éditeur
   sélectionnez dans la page "Data Access" de la Palette le composant DatasetProvider:
  • Posez-le sur la tForm.
  • dans sa propriété DataSet sélectionnez SqlQuery1
   sélectionnez dans la page "Data Access" de la Palette le composant ClientDataSet:
  • Posez-le sur la tForm.
  • dans sa propriété ProviderName sélectionnez DatasetProvider1
   sélectionnez dans la page "Data Access" de la Palette le composant DataSource:
  • Posez-le sur la tForm.
  • dans sa propriété DataSet sélectionnez ClientDataSet1
   sélectionnez dans la page "Data Controls" de la Palette le composant dbGrid:
  • Posez-le sur la tForm.
  • dans sa propriété DataSource sélectionnez DataSource1
   sélectionnez sur la Forme SqlClientDataSet1

   posez un tButton sur la Forme et faites lui ouvrir ClientDataset1:
    ClientDataset1.Open;
   posez un tButton sur la Forme et faites lui sauvegarder le contenu actuel de tClientDataset:
    ClientDataset1.SaveToFile('RESU.XML');
   affichez le contenu du fichier RESU.XML:
  • ajoutez u_c_cds_analyze_xml et u_cds_table à la liste des USES
  • posez un tButton sur la Forme et faites lui afficher le contenu du fichier:

    uses ... , u_c_cds_tableu_c_cds_analyze_xml ...

      procedure TForm1.display_xml_Click(SenderTObject);
        var l_c_tablec_table;
        begin
          with c_analyze_xml.create_analyze_xml('xml''resu.xml'do
          begin
            l_c_table:= c_table.create_table('table');
            analyze_xml(l_c_table);

            display_line;
            l_c_table.display_formatted_line_list;
            l_c_table.Free;
            Free;
          end// with c_analyze_xml
        end// display_xml_Click


   compilez et exécutez


A présent voici un exemple de manipulation:
   cliquez sur le bouton "open_cds_"
   l'application ouvre la table et l'affiche
   ajoutez une ligne dans le dbGrid

    6      Delphi Asp.Net      3      1.400

et postez la en remontant dans la dbGrid

   cliquez "save_as_xml_" pour sauver le contenu de ClientDataSet dans le fichier RESU.XML

   allez dans le répertoire de l'exécutable et cliquez RESU.XML
   Internet Explorer affiche le contenu du fichier .XML:

   cliquez "display_xml_" pour visualiser les lignes de ce fichier .XML:
   le programme affiche ce contenu:


Vous pouvez ainsi effectuer toutes les opérations (ajout, effacement, modifications) que vous souhaitez, en prenant bien soin de:

  • provoquer le ClientDataSet.Post par changement de ligne dans le dbGrid après les modification
  • en sauvegardant le résultat de ces modifications en cliquant "save_as_xml_" si vous souhaitez conserver ces modifications


8.3 - Principe de la mise à jour du Serveur

Pour que les modifications effectuées (ajout, effacement, modification) soient enregistrées dans la Table du Serveur Interbase, il suffit d'appeler:

     nombre_erreurs:= tClientDataSet.ApplyUpdates(-1);

Le tClientDataSet va alors envoyer vers le Serveur (via le tDatasetProvider, le tSqlQuery, le tSqlConnection) les instructions SQL qui effectueront les modifications.

Si le Serveur ne peut effectuer la modification demandée, il retourne une erreur en indiquant le motif du refus. Pour gérer ces les erreurs, nous pouvons créer l'événement OnReconcileError du tClientDataset. Les paramètres sont les suivants:

    PROCEDURE ReconcileError(
p_c_data_set: TCustomClientDataSet;
p_c_error: EReconcileError;
p_update_kind: TUpdateKind;
VAR pv_action: TReconcileAction);

et:

  • p_c_data_set: c'est le tClientDataSet qui contient les erreurs
  • p_c_error: une exception contenant dans p_c_error.Message le texte de l'exception
  • p_update_kind indique si l'erreur provenant d'un ajout (ukInsert), d'une modification (ukModify) ou d'un effacement (ukDelete)
  • pv_action: indique ce qu'il faut faire de cette erreur (annuler, forcer la valeur etc)
Et Borland suggère même d'utiliser un dialogue tout préparer pour proposer à l'utilisateur de décider ce qu'il faut faire en lui présentant directement le problème et le le laissant choisir le traitement à effectuer. Ce dialogue appelé RECERROR.PAS se trouve dans:

C:\PROGRAM FILES\BORLAND\DELPHI6\OBJREPOS

et a l'allure suivante:

et:

  • il affiche l'erreur
  • présente les données ayant causé la faute
  • laisse l'utilisateur décider ce qu'il souhaite faire
Pour utiliser ce dialogue:
  • il faut créer l'événement tClientDataset.OnReconcileError
  • ouvrir le dialogue RecError et récupérer le choix de l'utilisateur en appelant HandleReconcileError en retournant cette valeur dans pv_action:

         pv_action:= HandleReconcileError(p_c_data_set, p_update_kind, p_c_error);

Finalement pour comprendre les commandes que Delphi génère pour nous lors de l'appel de tClientDataset.ApplyUpdates, nous allons employer un SqlMonitor qui affiche les API Interbase envoyées vers le Serveur Interbase. Pour cela il suffit de
  • relier le tSqlMonitor à tSqlConnection
  • basculer tSqlMonitor sur True
  • créer l'événement tSqlMonitor.OnLogTrace et visualiser tSqlMonitor.TraceList


Ajoutons ces éléments à notre application:
   posez un tButton "ApplyUpdates_" sur la Forme et faites lui mettre à jour le serveur:

    procedure TForm1.ApplyUpdates_Click(SenderTObject);
      var l_erreursInteger;
      begin
        l_erreurs:= ClientDataSet1.ApplyUpdates(-1);
        display('erreurs 'IntToStr(l_erreurs));
      end// ApplyUpdates_Click

   ajoutez à la liste des USES la forme de réconciliation:

     USES .... recerror, ...

   créez le gestionnaire d'erreur:
  • sélectionnez ClientDataset1
  • créez son événement OnReconcileError
  • appelez la Forme de réconciliation et retournez le code à Delphi:

    procedure TForm1.ClientDataSet1ReconcileError(
        DataSetTCustomClientDataSetEEReconcileError;
        UpdateKindTUpdateKindvar ActionTReconcileAction);
      begin
        action:= HandleReconcileError(DataSetUpdateKinde);
      end// ClientDataSet1ReconcileError

   ajoutez la trace Sql:
  • sélectionnez dans la page "dbExpress" de la Palette le composant SqlMonitor:

  • Posez-le sur la tForm.
  • sélectionnez SqlConnection et mettez-y SqlConnection1
  • sélectionnez Active et basculez le sur True
  • créez son événement OnLogTrace et affichez les textes:

        var g_trace_countInteger= 0;

        procedure TForm1.SQLMonitor1LogTrace(SenderTObject;
            CBInfopSQLTRACEDesc);
          begin
            with SQLMonitor1TraceList do
              while g_trace_countCount do
              begin
                display(Strings[g_trace_count]);
                Inc(g_trace_count);
              end;
          end// SQLMonitor1LogTrace


   compilez


8.4 - Chargement du fichier .XML

Si nous avons sauvegardé dans un fichier .XML un tClientDataSet qui a été modifié via le dbGrid (et que les modifications n'ont pas encore été envoyée au Serveur), nous pouvons charger les données du fichier .XML en appelant:

     tClientDataset.LoadFromFile(nom_fichier);

Les données originales et TOUTES les modifications sont alors chargées dans le tClientDataset.

Ajoutons cette possibilité à notre application:
   posez un tButton "load_xml_" sur la Forme et faites lui charge le fichier .XML:
    ClientDataset1.LoadFromFile('resu.XML');
   compilez


8.5 - Mise à jour d'un ajout

Prenons tout d'abord le cas d'un ajout. Nous avons vu que l'instruction SQL pour ajouter est du type:

 INSERT INTO FORMATIONS
     (f_numero, f_nom, f_jours, f_prix)
     VALUES (6, 'ASP.Net Delphi', 3, 1400)

Vérifions que dbExpress génère bien une instruction de ce type pour ajouter notre ligne à la Table:
   chargez le fichier .XML en cliquant "load_xml_" (ou bien insérez une nouvelle ligne dans le dbGrid)
   la dbGrid affiche les données chargées dans ClientDataset1. Dans notre exemple, il y a juste une ligne ajoutée (6 / ASP.Net Delphi / 3 / 1400)
   envoyez la ligne ajoutée au Serveur en cliquant "ApplyUpdates_"
   l'ajout et effectué en SqlMonitor1 affiche les instructions Interbase utilisés par Delphi pour cet ajout:

Nous retrouvons bien, au milieu des isc_ de prologue et de fermeture, notre "INSERT INTO...". Notez que les "?" tiennent lien des paramètres, le moniteur ne jugeant pas utile d'afficher les valeurs littérales des champs.



ATTENTION: dans l'instruction SQL de SqlQuery, nous avons placé:

 SELECT * FROM FORMATIONS

et non PAS

 SELECT * FROM formations

La différence est capitale:

  • nous avons bien créé la table avec un nom minuscule (pour nos présentations, nous préférons réserver les majuscules pour les mots clés, et utiliser les minuscules pour les identificateurs de notre cru)
  • comme nous sommes en "dialecte 3" la casse est significative: le Serveur considère formation et FORMATIONS comme deux tables distinctes
  • hélas, hélas, hélas ... Quelque part dans la chaîne dbExpress, les minuscules formations, f_numero, f_nom, f_jours, f_prix sont mystérieusement transformées en majuscules, et à l'insu de mon plein gré !
  • l'Explorateur de Bases de données nous le confirme:

  • les SELECT se déroulent sans problème, le même passage en majuscule étant mystérieusement effectué
  • pour les modifications en revanche, l'utilisation de formations au lieu de FORMATIONS provoque une erreur qui est affichée dans OnReconcileError sous "table inconnue"

    3 heures pour trouver l'origine du problème et comprendre ce traficotage dans notre dos...



8.6 - Effacement

Pour effacer une ligne, il faut envoyer l'instruction Sql:

 DELETE  
     FROM FORMATIONS
     WHERE f_numero= 2

Le WHERE est optionnel, mais si vous ne dites pas comment le Serveur doit sélectionner la ligne à effacer, il effacera tout.

Quel est le code utilisé par dbExpress ? Eh bien il suffit de regarder:
   allez dans le dbGrid sur la ligne "4 / Composants Delphi" et supprimez la ligne en tapant

Ctrl + Suppression

(enfoncez la touche Contrôle et la touche Suppression tout en maintenant Ctrl enfoncée)

   Delphi vous demande de confirmer : "Delete Record ?"
   cliquez Ok
   la ligne disparaît du dbGrid
   examinez, par curiosité, le contenu du tClientDataset
  • cliquez "save_as_xml_"
  • cliquez "display_xml_"

La ligne a bien disparu du dbGrid et le code de la ligne dans le fichier .XML est le code d'une ligne effacée

   envoyez l'effacement vers le Serveur en cliquant "ApplyUpdate_"


Nous constatons que dbExpress a utilisé dans WHERE TOUS les champs de la table. Ceci signifie que:

  • la ligne n'est effacée que si elle a conservé la valeur qu'elle avait lorsque nous l'avons chargé (depuis le Serveur ou depuis le fichier .XML)
  • si un autre utilisateur avait changé la moindre valeur (le numéro de ligne, un accent aigu quelque part, une décimale du prix...) le Serveur n'aurait pas pu trouver la ligne et ne l'aurait pas effacée.
Nous pouvons vérifier ceci en "simulant" l'action d'un second utilisateur qui va modifier une ligne:
   exécutez le programme et ouvrez le ClientDataset (par "open_cds_" ou "load_xml_")
   faites modifier une valeur par un SECOND utilisateur
  • ouvrez un Explorateur Windows
  • lancez P_IB_DBX_MODIFY.EXE
  • sélectionnez la ligne "1 / Initiation Delphi", modifiez le prix en 1350, et postez en changeant de ligne
  • envoyez le changement vers le Serveur en cliquant "ApplyUpdates_"

  • fermez cet .EXE
   à présent essayez d'effacer la ligne modifiée par l'autre utilisateur dans le programme lancé depuis Delphi:
  • allez dans le dbGrid sur la ligne "1 / Initiation Delphi"
  • tapez
    Ctrl + Suppression
  • Delphi indique qu'il ne trouve pas la ligne ayant exactement les mêmes valeurs:

    Vous noterez que:

    • notre affichage (en arrière plan) indique bien:

          reconcile Record not found or changed by another user

      Pas de quoi être surpris, nous avons tout fait pour !

    • le dialogue:
      • présente le même message à l'utilisateur
      • affiche les valeurs actuelles de l'enregistrement
      • suggère l'action "Skip" (annuler l'effacement)
   cliquez sur "Ok", ce qui annulera l'effacement
   sauvegardes le contenu de ClientDataset1 en cliquant "save_as_xml_"
Aurions nous pu tout de même effacer l'enregistrement ? Oui, à condition de fournir une instruction Sql qui corresponde à un enregistrement existant. Dans notre cas, l'"autre" utilisateur n'a fait que changer le prix. Les autres champs n'ont pas été modifiés. Si nous effectuions une spécification de l'enregistrement par f_numero uniquement, le Serveur aurait bien pu trouver l'enregistrement numéro 2 et l'effacer.

Pour que dbExpress génère une requête d'effacement qui n'utilise que la colonne f_numero, il faut que nous modifions la valeur de DatasetProvider.UpdateMode. En effet:

  • par défaut, la valeur est UpWhereAll qui indique justement que le WHERE contiendra la valeur de tous les champs
  • si nous sélectionnons UpWhereKeyOnly, seule la clé sera utilisée pour construire la requête
Commençons par ajouter un index à notre table:
   créez une nouvelle application et nommez-la "ib_dbx_add_index"
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlConnection et posez-le sur la tForm.
Cliquez deux fois sur SqlConnection1, renseignez
     "Driver Name" -> Interbase
     "Connection Name" -> IbLocal
     "DataBase" -> ..\\data\\institut_pascal.gdb
     "Dialect" -> 3
     "User Name" -> SYSDBA
     "Pass Word" -> masterkey
Allez dans l'Inspecteur d'Objet:
  • sélectionnez LoginPrompt et basculez sa valeur sur False
  • vérifiez la connection en basculant Connected sur True, puis fermez la connection en basculant Connected sur False.
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery et posez-le sur la tForm.
  • Sélectionnez sa propriété SqlConnection et initialisez-la à SqlConnection1
   posez un tButton "add_index_" sur la Forme et faites lui créer notre index. L'instruction Sql est:

 CREATE INDEX x_numero ON formations

et la procédure sera:

    var g_trace_countInteger= 0;

    procedure TForm1.SQLMonitor1LogTrace(SenderTObject;
        CBInfopSQLTRACEDesc);
      begin
        with SQLMonitor1TraceList do
          while g_trace_countCount do
          begin
            display(Strings[g_trace_count]);
            Inc(g_trace_count);
          end;
      end// SQLMonitor1LogTrace

   puisque nous y sommes, autant ajouter la suppression de l'index: posez un tButton "drop_index_" sur la Forme et faites lui supprimer notre index:

 DROP INDEX x_numero

   compilez et exécutez


Nous aurons alors:

Las, après essai, il s'avère que l'index seul ne suffit pas: il faut une CLE PRIMAIRE (un index UNIQUE, NOT NULL). Et NOT NULL ne peut apparemment pas être ajouté à une table existante, sauf via la modification de la colonne RDB$NULL_FLAG de la colonne de la table RDB$RELATION_FIELDS, ce qui nous entraînerait trop loin. Nous avons donc choisi de recréer la table formations avec une clé primaire:
   créez une nouvelle application et nommez-la "ib_dbx_create_table_pk"
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlConnection et posez-le sur la tForm.
Cliquez deux fois sur SqlConnection1, renseignez
     "Driver Name" -> Interbase
     "Connection Name" -> IbLocal
     "DataBase" -> ..\\data\\institut_pascal.gdb
     "Dialect" -> 3
     "User Name" -> SYSDBA
     "Pass Word" -> masterkey
Allez dans l'Inspecteur d'Objet:
  • sélectionnez LoginPrompt et basculez sa valeur sur False
  • vérifiez la connection en basculant Connected sur True, puis fermez la connection en basculant Connected sur False.
   sélectionnez dans la page "dbExpress" de la Palette le composant SqlQuery et posez-le sur la tForm.
  • Sélectionnez sa propriété SqlConnection et initialisez-la à SqlConnection1
   posez un tButton "create_table_primary_key_" sur la Forme et faites lui créer une table avec une clé primaire. L'instruction Sql est:

 CREATE TABLE formations
     (
         f_numero INTEGER NOT NULL PRIMARY KEY,
         f_nom CHAR(23),
         f_jours SMALLINT,'
         f_prix NUMERIC(12, 2)
     )

et la procédure sera:

    procedure TForm1.create_table_with_primary_key_Click(SenderTObject);
      begin
        with SqlQuery1 do
        begin
          Close;

          with Sql do
          begin
            Clear;
            Add('CREATE TABLE formations');
            Add('  ('
                +'    f_numero INTEGER NOT NULL PRIMARY KEY,');
            Add('     f_nom CHAR(23),');
            Add('     f_jours SMALLINT,');
            Add('     f_prix NUMERIC(12, 2)');
            Add('  )');
          end// with Sql

          Try
            SqlConnection1.Open;

            ExecSql;

            display('  ok');
          except
            on eException do
              begin
                display('  *** pb_create 'e.Message);
              end;
          end;
        end// with SqlQuery1
      end// create_table_Click

   posez un tButton "drop_table_" sur la Forme pour pouvoir supprimer la table existante, et faites lui exécuter:

 DROP TABLE formations

   compilez et exécutez

   cliquez "drop_table_"
   cliquez "create_table_with_primary_key_"
Peuplez à nouveau la table:
   lancez l'exécutable "P_IB_DBX_INSERT_DATA.EXE" et générez les valeurs pour la table formations en cliquant "insert_batch_"


A présent essayons d'effacer notre ligne:
   réouvrez "ib_dbx_modify"
   modifiez le mode de génération de la requête d'effacement:
  • sélectionnez DatasetProvider1
  • sélectionnez sa propriété UpdateMode et donnez-lui la valeur UpWhereKeyOnly
   compilez et exécutez


Et pour effacer:
   exécutez le programme et chargez le ClientDataset depuis le Serveur en cliquant "open_cds_"

   simulez un second utilisateur qui modifie "2 / initiation Delphi":
  • ouvrez un Explorateur Windows
  • lancez P_IB_DBX_MODIFY.EXE
  • sélectionnez la ligne "1 / Initiation Delphi", modifiez le prix en 1350, et postez en changeant de ligne
  • envoyez le changement vers le Serveur en cliquant "ApplyUpdates_"
  • fermez cet .EXE
   retournez dans l'exécutable lancé depuis Delphi et effacez la ligne:
  • choisissez la ligne "2 / initiation Delphi" et supprimez la en tapant "CtrlSuppression" et "Ok"
  • envoyez l'effacement vers le Serveur en cliquant "ApplyUpdates_"
  • l'effacement est réalisé avec succès en utilisant une recherche avec f_numero uniquement:





8.7 - Modification de données

La modification se fait de façon similaire. L'instruction Sql à utiliser est:

 UPDATE formations
     SET f_nom= 'UML et Design Patterns Delphi'
     WHERE f_numero= 5

Le problème est donc le même que pour l'effacement:

  • si tDatasetProvider.UpdateMode a la valeur UpWhereAll, dbExpress va générer une instruction qui tente de remplacer la ligne en supposant que personne ne l'a modifié depuis notre lecture initiale
  • si tDatasetProvider.UpdateMode a la valeur UpWhereKeyOnly, dbExpress va générer une instruction qui tente de remplacer la ligne ayant la même clé primaire que notre ligne
  • si tDatasetProvider.UpdateMode a la valeur UpWhereChanged, dbExpress va générer une instruction qui tente de remplacer la ligne ayant dans les champs que nous avons modifié les valeurs que ces champs avaient avant modification
Prenons, par exemple, le cas UpWhereKeyOnly:
   réouvrez "ib_dbx_modify"
   modifiez le mode de génération de la requête d'effacement:
  • sélectionnez DatasetProvider1
  • sélectionnez sa propriété UpdateMode et donnez-lui la valeur UpWhereChanged
   compilez et exécutez
   sélectionnez une ligne dans le dbGrid, modifiez la valeur de f_prix, postez en changeant de ligne
   cliquez "ApplyUpdates_"





9 - Télécharger les Exemples

Nous avons placé tous les projets dans des .ZIP qui comprennent:
  • 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 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ées
  • 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.

Voici les .ZIP:



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 en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



10 - Conclusion

Lors de cette présentation de dbExpress, nous avons indiqué comment installer Interbase, créer une base, des tables, ajouter, lire, modifier et effacer des données.

Si vous avez des questions, vous pouvez:

  • vous reporter au livre Delphi dBase qui présente, par le détail, la gestion des composants visuels et les techniques de traitement des données (formatage, vérfications, calculs...)
  • pour les blobs, voyez Blobs Interbase
  • pour l'utilisation d'Interbase en mode Client Serveur, sans utiliser le BDE, voyez le Tutorial Interbase
  • l'affichage du contenu d'un fichier .XML correspondant à la sauvegarde locale d'un tClientDataset est présenté dans Affichage ClientDataset XML
  • la présentation Architecture du Moteur Interbase présente l'organisation du programme Interbase, correspondant aux sources fournies par Borland pour la version 6.
  • je présenterai à la conférence Borcon France 2004 le panorama des techniques d'accès aux données Interbase: les API isc, le BDE, Interbase Express, dbExpress, et pour Delphi 8, IBX.NET en mode VCL.NET et ADO.NET en mode Windows Forms.
  • vous pouvez aussi m'adresser vos questions par e-mail à jcolibri@jcolibri.com
Nous n'avons pas essayé d'être exhaustif dans la présentation. Lors des stages que j'anime personnellement à l'Institut Pascal, j'entre beaucoup plus dans le détail:
  • la gestion des multiples erreurs et exceptions qui interviennent lors d'applications utilisant des bases de données
  • les possibilités d'installer Interbase depuis Delphi, et d'interroger les paramètres de la Base ou du schéma depuis un programme Delphi, de lister les tables, les champs etc
  • le traitement des contraintes (clé primaires, intégrité référentielle, intégrité sémantique)
  • la manipulation détaillée des valeurs extraite d'une Table, en utilisant les tFields et les différents composants visuels tDb_xxx, ainsi que les traitements spécifiques aux chaînes, aux dates, aux blobs
  • les techniques de validation de saisie essentielles pour toute application de gestion
  • le traitement spécifiques à dbExpress:
    • l'utilisation de tClientDataset avec ses multiples possibilités
    • les paramétrage du tDatasetProvider
    • la gestion des erreurs de réconciliation et le traitement d'utilisateurs multiples
  • l'étude approfondie du langage SQL, qui est au coeur de tout traitement avec des Serveurs Sql
  • les triggers, les procédures cataloguées (tStoredProc), les générateurs
  • la gestion de Serveur (sauvegarde / restauration, statistiques, réglage)
Pour examiner le programme détaillé de cette formation, cliquez Client Serveur Interbase. Nous proposons de plus d'autres formations présentées dans ces pages (programme, dates des prochaines sessions, conditions, transparents, abonnement à la lettre d'information...).

Finalement j'interviens aussi pour des missions d'audit, de réalisation de projet, de portage et maintenance, et de tutorat.




11 - 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.
Créé: mar-04. Maj: aou-15  148 articles, 471 sources .ZIP, 2.021 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2015
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
      + programmation_oracle
      + interbase
        – interbase_blobs
        – interbase_tutorial
        – interbase_dbexpress
        – interbase_ibx_net
        – ib_dbexpress_net
        – delphi_8_ado_net
        – borland_data_provider
        – sql_script_extraction
        – interbase_udf
        – sql_script_executer
        – ib_blob_extraction
        – insert_blob_script
        – ib_stored_procedures
      + sql_server
      + firebird
      + mysql
      + xml
      – paradox_via_ado
      – mastapp
      – delphi_business_objects
      – clientdataset_xml
      – data_extractor
      – rave_report_tutorial
      – visual_livebindings
      – migration_bde
    + web_internet_sockets
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Expert Delphi Résolution de problèmes ponctuels, optimisation, TMA Delphi, audit, migration, réalisation de projets, transfert de technologie - Tél 01.42.83.69.36
Formation Delphi 7 complete L'outil de développpement, le langage de programmation, les composants, les bases de données et la programmation Internet - 5 jours
Formation Programmation SQL Le langage SQL - 3 jours
Formation Ecriture de Composants Delphi Creation de composants : étapes, packages, composants, distribution - 3 jours