menu
  Home  ==>  articles  ==>  bdd  ==>  sql_server  ==>  turbo_delphi_ado_net_bdp   

Turbo Delphi Ado Net BDP - John COLIBRI.

  • résumé : Programmation d'applications ADO Net d'une base SQL Server, en utilisant Turbo Delphi et le BDP
  • mots clé : SQL Server - ADO.Net - BDP - Borland Data Provider - Delphi - Windows Forms - SQL Serveur
  • logiciel utilisé : Windows XP Home - .Net SDK 1.1.432 - Turbo Delphi For Net
  • matériel utilisé : Pentium 2.800Mhz, 512 M de mémoire, 250 Giga disque dur
  • champ d'application : développeur base de données Windows .Net
  • niveau : développeur Delphi ou autre langage .Net
  • plan :


1 - Introduction

Nous allons présenter comment développer des applications de bases de données utilisant Firebird en mode ADO.Net avec Delphi.

Nous allons présenter

Nous ne pouvons pas tout présenter ici. Ne sont pas présentés dans ce document
  • les procédures cataloguées, les triggers, les séquences
  • les transactions et la gestion de la concurrence
  • le détail des contrôles visuels (TreeView, ListView etc)
Rappelons aussi que nous organisons tous les mois des formations, et en particulier pour ceux intéressés par les bases de données et .Net:


2 - Principe

2.1 - Architecture Client Serveur

Les applications de bases de données, que ce soit Sql Server, Oracle, Interbase, Firebird, MySql etc, fonctionnent essentiellement en mode Client Serveur. Dans ce modèle
  • un logiciel appelé le Serveur gère les transferts des données entre les applicatifs et le stockage disque
  • les applicatifs Client envoient des requêtes au Serveur qui leur fournit les données demandées.
En supposant, ce qui est fréquemment le cas, que le Server et les Clients soient sur des PC différents, la communication entre les deux transite par un réseau (TCP/IP ou autre).

Schématiquement nous avons donc

  • le Serveur qui comporte
    • le stockage disque
    • le logiciel Serveur (le Serveur SQL Server pour nous)
    • les couches réseau
    image

  • un ou plusieurs Clients contiennent
    • les couches réseau (Tcp/Ip ici)
    • le client (le Client SQL Server dans notre cas)
    • les composants de bases de données (ADO.Net)
    • un ou plusieurs logiciels applicatifs (une comptabilité, un portail Web etc)
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 SQL Server
  • le Client SQL Server
  • les composants d'accès aux données (.Net Framework et ADO.Net)
  • l'outil de développement (Delphi 2005)
Nous avons déjà présenté comment installer SQL Server. Pour les experts
  • vous cherchez le logiciel via Google
  • vous le téléchargez
  • vous lancez l'installation
Si cela ne fonctionne pas, voyez l'article cité qui explique tout par le menu détail, avec les tests à toutes les étapes.

Pour le .Net Framework, il est installé lors de l'installation de Delphi 2005, Delphi 2006 ou Turbo Delphi for .Net. Sinon Google regorge de tutoriaux qui vous diront comment faire.

Le .Net Framework installé avec Delphi vous permet d'utiliser les bases Interbase, Sql Server et Oracle



2.2 - Organisation des répertoires

Nous allons utiliser une base de données que nous placerons en

    c:\programs\fr\bdd\turbo_delphi_bdp\_data




3 - Architecture ADO .Net

Nous allons présenter quelques applications qui vont
  • créer et remplir des tables
  • afficher le résultat dans des composants visuels (DataGrid)
  • mettre à jour les données depuis les composants visuels.
Nous utiliserons les différents composants ADO.Net. Mais avant de plonger dans le code, présentons l'architecture de ces composants.



3.1 - Structure Globale

ADO.Net est composée de 4 catégories de composants:
  • des composants qui dialoguent directement avec la base de données pour assurer la connexion et exécuter les requêtes SQL. Ces composants forment le DataProvider

    image

    et:

    • BdpConnection permet d'établir la connexion avec le Serveur
    • BdpCommand est utilisé pour lancer les requêtes (SELECT ou CREATE, INSERT etc)
    • BdpDataReader est utilisé pour récupérer et tamponner les SELECT. Ces données peuvent ensuite être manipulées par programme (calculs, affichage dans un TextBox)
  • un composant chargé de transférer les données entre le DataProvider et les composants qui stockent les données en mémoire: c'est le BdpDataAdapter. Il contient essentiellement
    • quatre BdpCommand, permettant chaque type de commande SQL possible: SELECT, INSERT, DELETE, UPDATE
    • un composant TableMappings qui permet de donner des noms "lisibles" aux noms attribués par défaut par ADO.Net (Table1, Table2 etc peuvent être remplacés par Facture ou Livraisons)
    soit:

    image

  • des composants qui stockent en mémoire les données fournies:
    • par le BdpDataAdapter
    • par une lecture d'un fichier disque (.XML par exemple)
    • par des instructions de code
    Ces composants sont appelés DataSet, et comportent
    • des tables DataSet (provenant de table du Serveur)
    • des contraintes (NOT NULL etc)
    • des relations (clés étrangères pour établir des relations maître détail)
    • des vues, permettant le filtrage, la projection, le tri, les agrégats, les recherches etc
    Soit:

    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 BdpDataAdapter que nous avons présenté ci dessus permet
    • le chargement des données depuis le Serveur dans le DataSet: c'est l'instruction BdpDataAdapter.Fill()

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

    image


Que vient faire le BdpDataProvider dans cette affaire ? Eh bien le .Net Framework et Delphi sont livrés avec des DataProvider standards tels que
  • un provider pour Sql Server (SqlConnection, SqlCommand, SqlDataAdatper etc)
  • un provider pour OleDb. OleDb est la mécanique qui permettait via COM l'accès à "toute" source de données: serveur SQL naturellement, mais aussi mail, Excel etc. Donc ADO.Net permet de récupérer ce type de connection via un DataProvider
  • un Borland Data Provider qui est une généralisation du DataProvider, et que nous avons déjà présenté

    image

Pour accéder aux données SQL Server nous pouvons donc utiliser plusieurs chemins:

image



Dans cet article, nous nous intéresserons à la programmation directe ADO.Net, en utilisant le BDPDataProvider.




4 - La programmation ADO.Net



4.1 - Les paramètres de notre SQL Server

Les paramètres que nous avons fournis lors de l'installation de SQL Server sont les suivants:
  • HostName: pol_1400_5\my_instance
  • User: sa
  • Password: my_pass
Vous remplacerez ces valeurs par celles de votre moteur SQL Server



4.2 - La création de la base

Pour créer une base SQL Serveur, il faut envoyer la requête CREATE DATABASE avec des paramètres propre à ce moteur. Nous utiliserons la requête suivante:

 
CREATE DATABASE order_entry
  ON PRIMARY
    (
      NAME = 'the_order_entry_data',
        FILENAME= 'c:\programs\fr\bdd\sql_server\turbo_delphi_bdp\_data\the_order_entry_data.mdf'
    )
  LOG ON
    (
      NAME = 'the_order_entry_log',
        FILENAME ='c:\programs\fr\bdd\sql_server\turbo_delphi_bdp\_data\the_order_entry_log.ldf'
    )

Cette requête est utilisée de la façon suivante:

  • nous nous connectons à une base système appelée MASTER. Cette connection est effectuée en utilisant un BdpConnection
  • la requête elle-même est placée dans un BdpCommand qui est envoyé vers le Serveur en appelant ExecuteNonQuery
Voici l'application:
   lancez Delphi

   Delphi présente l'IDE

image



Nous allons commencer par vérifier que nous arrivons à nous connecter à MASTER. Pour cela, nous allons utiliser l'Explorateur de bases de données, situé dans le NoteBook en haut à droite, troisième onglet:
   dans le NoteBook en haut à droite, sélectionnez l'onglet "Data Explorer"

image

   sélectionnez MSSQL, effectuez un click droit, et sélectionnez "Add New Connection"

   Delphi demande quel nom vous souhaitez donner à cette connection dans le tTreeView:

image

   tapez un nom de connection, par exemple

     master_connection

et cliquez "Ok"

   une nouvelle connection est ajoutée

   faites un clic droit souris, et sélectionnez "modify connection"

   un Editeur de connection est affiché:

image

   tapez les valeurs pour DataBase (MASTER), HostName (pol_1400_5\my_instance), l'utilisateur (sa) et le mot de passe (my_pass)

Cliquez "Ok"

Réouvrez l'Editeur (clic droit, "modify connection" et cliquez "Test"

   la connexion est confirmée:

image



Voici comment créer la base:
   créez une nouvelle application Windows Forms : "file | New | Delphi Windows Forms", et renommez-la "p_01_bdp_create_database"

image

   dans le Data Explorer, sélectionnez "MSSQL | master_connection" et tirez-le SUR LA FORME

   Delphi affiche BdpConnection1 dans la zone des composants non-visuels, SOUS la Forme:

image

   posez un tButton sur la Forme, renommez-le "create_database_", créez son événement OnClick et écrivez le code qui va créer la base:

const k_database_name'order_entry';
      k_path'c:\programs\fr\bdd\sql_server\turbo_delphi_bdp\_data\';
      k_create_database=
          ' CREATE DATABASE order_entry'
                   + ' ON PRIMARY '
                   + '   ('
                   + '      NAME = ''the_order_entry_data'' '
                   + '      , FILENAME = '''k_path
                   +            'the_'k_database_name'_data.mdf'''
                   + '   ) '
                   + ' LOG ON '
                   + '   ('
                   +'       NAME = ''the_order_entry_log'' '
                   + '      , FILENAME = '''k_path
                   +            'the_'k_database_name'_log.ldf'' '
                   + '   ) '
                   ;

procedure TWinForm.create_database___Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_bdp_commandBdpCommand;
  begin
    BdpConnection1.Open;
    l_c_bdp_command:= BdpCommand.Create(k_create_databaseBdpConnection1);
    l_c_bdp_command.ExecuteNonQuery();
    BdpConnection1.Close
  end// create_database___Click

   compilez, exécutez et cliquez "create_database_"
   voici le répertoire contenant notre nouvelle base:

image



Vous pouvez télécharger le .ZIP 01_bdp_create_database.zip qui contient en outre des boutons pour afficher toutes les tables connues de SQL Server (MASTER, ORDER_ENTRY etc), tester la connexion, effacer la base (DROP DATABASE .) Comme ces options utilisent des techniques présentées ci-dessous, nous ne rentrerons pas dans le détail du code



4.3 - La connection

4.3.1 - Les différentes méthodes

Nous allons commencer par établir la connexion entre notre application Delphi et la base ORDER ENTRY située sur le Serveur. Nous examinerons plusieurs techniques:
  • le Data Explorer
  • du code Delphi
  • le composant BDP


4.3.2 - Connection dans le Data Explorer

Nous allons ajouter une entrée dans le Data Explorer pour nous connecter à notre nouvelle base (c'est la même manipulation que pour ajouter MASTER):
   dans le NoteBook en haut à droite, sélectionnez l'onglet "Data Explorer"

image

   sélectionnez MSSQL, effectuez un click droit, et sélectionnez "Add New Connection"

   Delphi demande quel nom vous souhaitez donner à cette connection dans le tTreeView. Tapez un nom de connection, par exemple

     order_entry_connection

et cliquez "Ok"

   une nouvelle connection est ajoutée

   faites un clic droit souris, et sélectionnez "modify connection"

   un Editeur de connection est affiché:

   tapez les valeurs pour DataBase (ORDER_ENTRY), HostName (pol_1400_5\my_instance), l'utilisateur (sa) et le mot de passe (my_pass)

image

Cliquez "Ok"

Réouvrez l'Editeur (clic droit, "modify connection" et cliquez "Test"

   la connexion est confirmée


4.3.3 - Utilisation de BdpConnection

Nous souhaitons à présent nous connecter à la base ORDER_ENTRY dans nos propres projets. Nous utiliserons un composant de connexion, représenté en rouge dans ce schéma d'architecture:

image



4.3.4 - La connexion par code

Nous allons à présent réaliser la connexion par code uniquement. Pour cela, il faut:
  • créer un composant BdpConnection
  • remplir la propriété ConnectionString
Voici le projet:
   créez une nouvelle application Windows Forms : "file | New | Delphi Windows Forms", et renommez-la "p_11_connect_order_entry"

   posez un Button sur la Forme, renommez-le "connect_", créez son événement OnClick et écrivez le code qui crée un BdpConnection, initialise ConnectionString et ouvre la connexion:

const k_database_name'order_entry';

      k_user_sa'user id=sa';
      k_assembly'assembly=Borland.Data.Mssql, Version=2.5.0.0, Culture=neutral, '
          + 'PublicKeyToken=91d62ebb5b0d1b1b';
      k_password_me'Pwd = my_pass';
      k_sspi_security'integrated security=SSPI';
      k_server_name'SERVER= "pol_1400_5\my_instance"';
      k_database_parameter'DATABASE= 'k_database_name;

      k_connection_string=
            k_user_sa
            + ';'k_password_me
            + ';'k_sspi_security
            + ';'k_server_name
            + ';'k_assembly
            + ';'k_database_parameter;

procedure TWinForm1.connect__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_bdp_connectionBdpConnection;
  begin
    display('> connect');
    display(k_connection_string);

    try
      l_c_bdp_connection:= BdpConnection.Create(k_connection_string);
      display('ok_success')
    except
      on eException do
        display('*** err 'e.Message);
    end;

    display('< connect');
  end// connect__Click

   compilez, exécutez et cliquez "connect_"
   voici une vue du projet:

image



4.3.5 - Utilisation du composant BdpConnection

Au lieu de créer le composant BdpConnection, nous pouvons le déposer depuis la Palette:
   fermez les onglets de la Palette (click droit et "collapse all", et ouvrez l'onglet "Borland Data Provider"

Sélectionnez BdpConnection:

image

et posez le composant SUR LA FORME

   Delphi l'affiche dans la zone des composants non-visuels (flèche rouge)

image

   pour initialiser les paramètres de BdpConnection1, cliquez le lien "Connection Editor" situé sous l'Inspecteur d'Objet (flèche jaune), ou bien utilisez clic droit souris sur BdpConnection1, "connection editor"

   l'Editeur de Connection BDP s'affiche:

image

Par défaut, la première entrée est affichée (Interbase)

   sélectionnez "order_entry_connection", et cliquez "test" pour vérifier la connexion


4.3.6 - Utilisation du Data Explorer

Voici une autre façon encore de créer et initialiser un BdpConnection:
   dans le NoteBook en haut à droite, sélectionnez "Data Explorer | MSSQL | order_entry_connection" et tirez cette connexion sur la Forme

   Delphi crée un BdpConnection qui est déjà entièrement initialisé (ouvrez l'Editeur de connexion BDP et cliquez "Test" pour vous en assurer)


4.3.7 - Quelle technique de connexion utiliser ?

Assurément le "tirer-glisser" de la connexion depuis le Data Explorer est la façon la plus efficace. Elle comporte quelques inconvénients:
  • il faut créer une entrée
  • certains développeurs sont réticents à utiliser l'Explorateur
  • tous les moteurs n'ont pas encore la possibilité d'utiliser l'Explorateur (Firebird, par exemple)
Les autres techniques présentées peuvent alors être utilisées.

Dans le reste de cet article, nous utiliserons l'Explorateur

Pour télécharger le source, cliquez p_01_connect.zip




4.4 - Création de table

4.4.1 - CREATE TABLE

Nous allons placer dans notre base des tables pour gérer une petite facturation. La base contiendra deux tables
  • un table de factures, INVOICE
  • une table des articles de la facture, ITEM
Voici le schéma de nos tables:

image

Pour créer la table INVOICE, nous envoyons vers SQL Server la requête SQL suivante:

 
CREATE TABLE invoice
  (
     i_id INTEGER NOT NULL PRIMARY KEY,
     i_customer CHARACTER(7),
     i_date DATETIME
  )

Notez que le préfixe "i_" pour désigner les champs de Invoice n'est pas nécessaire.



Pour envoyer cette requêtes vers le Serveur, nous utiliserons les composants suivants:

  • BdpConnection pour la connexion
  • BdpCommand pour exécuter la requête SQL de création
Soit schématiquement:

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 la propriété CommandText d'un composant SqlCommand. Cela peut être effectué en même temps que la création du BdpCommand:

    my_c_command:= BdpCommand.Create('CREATE TABLE ...'BdpConnection1);

    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.4.2 - La création

Par conséquent:
   créez une nouvelle application Delphi et renommez-la "p_21_bdp_create_table"
   dans le NoteBook en haut à droite, sélectionnez "Data Explorer | MSSQL | order_entry_connection" et tirez cette connexion sur la Forme
   Delphi place un BdpConnection1 tout initialisé dans la zone des composants non-visuels
   posez un tButton sur la Forme, renommez-le "create_invoice_", créez son événement OnClick et écrivez le code qui va envoyer la requête CREATE TABLE vers le Serveur:

const k_create_invoice_table=
           'CREATE TABLE invoice 'k_new_line
         + '  ('k_new_line
         + '      i_id INTEGER NOT NULL PRIMARY KEY'k_new_line
         + '    , i_customer CHARACTER(7)'k_new_line
         + '    , i_date DATETIME'k_new_line
         + '  )';

procedure TWinForm.create_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_commandBdpCommand;
      l_resultInteger;
  begin
    BdpConnection1.Open();

    l_c_command:= BdpCommand.Create(k_create_invoice_tableBdpConnection1);
    l_result:= l_c_command.ExecuteNonQuery();
    display('Result 'l_result.ToString);
    BdpConnection1.Close();
  end// create_invoice_Click

   compilez, exécutez et cliquez "create_invoice_"
   voici une vue du projet:

image



Notez que:
  • au lieu de créer BdpCommand par code, nous aurions pu utiliser le composant de la Palette


Nous pouvons visualiser la nouvelle table dans l'Explorateur de données:
   dans l'Explorateur, sélectionnez "MSSQL | order_entry_connection"
   sur un clic droit souris, sélectionnez "Refresh"
   sélectionnez "Tables | dbo.Invoice"
   notre table et ses champs sont affichés

image



4.4.3 - DROP TABLE

Nous ajoutons immédiatement l'instruction SQL qui nous permettra de supprimer la table, pour pouvoir effectuer autant de modifications que nous souhaitons. La requête SQL est:

 
DROP TABLE invoice

Nous avons donc ajouté un nouveau bouton pour permettre l'effacement de la table.



Notez que :

  • nous avons placé toutes nos requêtes dans des constantes Pascal. Ceci nous permet de trouver tout le SQL à un seul endroit de notre application et permet plus facilement de vérifier sa cohérence ou de le modifier par couper / coller, mise en commentaire etc
  • notre application comporte aussi
    • un TextBox pour afficher les messages de mise au point (unité u_c_dot_net_display qui se trouve avec chaque .ZIP)
    • une CheckBox qui nous permet d'afficher le SQL que nous envisageons d'envoyer. En effet la constant Delphi comporte des guillemets Delphi, et éventuellement des guillemets SQL, et il arrive d'en oublier. Une vérification préalable permet de gagner du temps
  • nous aurions aussi pu ajouter des exceptions pour piéger nos erreurs, mais cela est trivial
  • nous avons aussi ajouté des méthodes pour créer et effacer la table des ITEMS
  • le projet affiche aussi le schéma des tables créées


Vous pouvez télécharger le projet p_21_bdp_create_table.zip



4.4.4 - Ajout de données

Les autres instructions de modifications de TABLE (écriture de données, modification de valeurs, effacement de ligne) utilisent les mêmes composants ADO.Net que ceux de la création.

Pour ajouter un nouveau stage, la requête SQL est la suivante:

 
INSERT INTO invoice
  (i_idi_customeri_date)
  VALUES (101, 'Smith', '9/21/2006')

Nous pouvons utiliser la même technique que précédemment, mais cela nous oblige à créer une requête pour chaque ligne. Nous avons choisi d'utiliser une procédure Delphi paramétrée ce qui nous permet d'ajouter une ligne avec une seule instruction Delphi:

  • dans la méthode Click, nous appelons des lignes telles que:

    fill_the_invoice(100, 'Smith',     '9/12/2006');
    fill_the_invoice(101, 'DevShop',   '9/14/2006');
    fill_the_invoice(102, 'EastMfg',   '9/14/2006');

  • la procédure fill_the_invoice est:

    procedure fill_the_invoice(p_idIntegerp_customerp_dateSystem.String);
      var l_valuesl_requestSystem.String;
      begin
        l_values:= p_id.ToString
          + ', '''p_customer''''
          + ', '''p_date'''';
        l_request:= 'INSERT INTO invoice '
          +  ' (i_id, i_customer, i_date) 'k_new_line
          +  '    VALUES ('l_values')';

        execute_non_query(do_execute_.Checkedl_request);
      end// fill_the_invoice

  • et execute_non_reader est notre procédure générique d'écriture:

    procedure TWinForm.execute_non_query(p_do_executeBoolean;
        p_requestSystem.String);
      var l_c_commandBdpCommand;
          l_countInteger;
      begin
        if p_do_execute
          then begin
              l_c_command:= BdpCommand.Create(p_requestBdpConnection1);
              Try
                l_count:= l_c_command.ExecuteNonQuery();
              except
                on eexception do
                  display_bug_stop(e.Message);
              end;
            end;
      end// execute_non_query



Par conséquent:
   créez un nouveau projet et nommez-le "p_22_bdp_fill_table"
   dans l'Explorateur sélectionnez la connexion order_entry et tirez la sur la Forme
   posez un Button sur la Forme et créez son clic. Tapez les instructions qui remplissent la table en utilisant la procédure Delphi paramétrée présentée ci-dessus:

procedure TWinForm.fill_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);

  procedure fill_the_invoice(p_idIntegerp_customerp_dateSystem.String);
    var l_valuesl_requestSystem.String;
    begin
      l_values:= p_id.ToString
        + ', '''p_customer''''
        + ', '''p_date'''';
      l_request:= 'INSERT INTO invoice '
        +  ' (i_id, i_customer, i_date) 'k_new_line
        +  '    VALUES ('l_values')';

      execute_non_query(do_execute_.Checkedl_request);
    end// fill_the_invoice

  begin // fill_invoice__Click
    BdpConnection1.Open();

    fill_the_invoice(100, 'Smith',     '9/12/2006');
    fill_the_invoice(101, 'DevShop',   '9/14/2006');
    fill_the_invoice(102, 'EastMfg',   '9/14/2006');

    BdpConnection1.Close();
  end// fill_invoice__Click

   compilez, exécutez et cliquez "fill_invoice_"
   voici une vue du projet:

image



Notez que
  • c'est dans execute_non_query centralisée que nous effectuons nos affichages de contrôle
  • la création des valeurs VALUE nécessite quelques précautions:
    • les chaînes doivent être entourées de guillemets simples
    • la conversion de valeur réelles nécessite de batailler avec le caractère de décimales (PI aux US se note 3.13, mais3,14 en France et la routine de conversion suppose que nous tapons 3.14 sur un PC avec un XP Français. Nous envoyons donc une valeur Delphi 3.14, mais la conversion la change en '3,14' que nous nous empressons de transformer en '3.14')
    • les dates doivent être au format US (mois/jour/année) et doivent être entre guillemets (sinon SQL effectue un division !)
    Tout ce petit travail est réalisé par notre procédure fill_the_training


Nous pouvons aussi afficher les données ajoutées dans l'Explorateur:
   dans l'Explorateur, sélectionnez "MSSQL | order_entry_connection"
   sur un clic droit souris, sélectionnez "Refresh"
   sélectionnez "Tables | dbo.Invoice" et cliquez "dbo.Invoice"
   les données de INVOICE sont affichées dans une DataGrid

image



4.4.5 - Effacement de la Table

Pour pouvoir effectuer plusieurs essais, nous avons ajouté une requête permettant de vider la table.

L'effacement de lignes se fait par la requête DELETE, qui pratiquement toujours comporte un clause WHERE permettant de spécifier quelles lignes nous souhaitons effacer. Pour effacer la ligne ayant l'identificateur 105, nous utiliserions:

 
DELETE 
  FROM invoice
  WHERE i_id= 105

Comme dans notre cas nous souhaitons purger TOUTES les lignes, nous supprimons simplement la clause WHERE (effacement inconditionnel).

Donc
   ajoutez un Button, et placez-y l'instruction d'effacement qui effacera toutes les lignes de la TABLE présentée ci-dessus, puis compilez et exécutez:

const k_delete_all_invoice'DELETE FROM invoice';

procedure TWinForm.delete_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    BdpConnection1.Open();

    execute_non_query(do_execute_.Checkedk_delete_all_invoice);

    BdpConnection1.Close();
  end// delete_invoice__Click




Notre projet permet:

  • l'ajoute, et l'effacement de la table ITEM
  • l'affichage du contenu des table dans une TextBox, mais ceci va être présenté plus bas.
Vous trouverez ce projet dans p_22_bdp_fill_table.zip



4.4.6 - Modifier une donnée

Pour modifier la valeur de données, nous utilisons UPDATE:

 
UPDATE invoice
  SET i_customer= 'Martin'
  WHERE i_customer= 'Smith'



Nous avons placé le code dans un nouveau projet:
   créez un nouveau projet et nommez-le "p_23_update_table"
   dans l'Explorateur sélectionnez la connexion order_entry et tirez la sur la Forme
   posez un Button sur la Forme et créez son clic. Tapez les instructions qui modifient des lignes, par exemple en "Smith" en "Martin":

const k_update_invoice=
            'UPDATE invoice' + k_new_line
          + '  SET i_customer= ''Martin''' + k_new_line
          + '  WHERE i_customer= ''Smith''';

procedure TWinForm.update_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    BdpConnection1.Open();
    execute_non_query(do_execute_.Checkedk_update_invoice);
    BdpConnection1.Close();
  end// update_invoice__Click

   compilez, exécutez et cliquez "update_invoice_"
   voici une vue du projet:

image



4.4.7 - Requête paramétrée

Lorsque nous envoyons une requête complexe (ce qui n'a pas été le cas ci-dessus), le Serveur essayera d'optimiser l'ordre des opérations. Cette optimisation, pour des requêtes impliquant de nombreuses TABLEs, peut prendre des heures. Il est alors recommandé de procéder en 2 temps:
  • nous envoyons un requête contenant des paramètres qui ne sont pas encore spécifiés en utilisant une commande Prepare() :

    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


Il faudra donc
  • envoyer une requête incomplète, avec des paramètres (comme des sortes de variables)
  • pour exécuter la requête:
    • donner des valeurs littérales à ces paramètres
    • exécuter la requête
Pour le BDP, les paramètres de la requête sont désignés par "?". Par exemple:

 
UPDATE invoice
  SET i_customer= ?
  WHERE i_id= ?

Et pour fournir la valeur des paramètres, nous utilisons la propriété Parameters de SqlCommand:

var my_c_parameterBdpParameter;

my_c_parameter:= BdpCommand.Parameters.Add('i_customer'BdpType.String);
my_c_parameter.Value:= 'PeterCo';

my_c_parameter:= BdpCommand.Parameters.Add('i_id'BdpType.Int32);
my_c_parameter.Value:= 101;

Notez que la notation pour désigner les paramètres dépend des composants d'accès ("?" pour BdpCommand, mais @my_param pour SqlCommand). Et les types sont aussi différents (BdpType.Int32, mais FbDbType.VarChar pour FireBird).



Voici le code complet
   ajoutez un Button, nommez-le "prepare_", créez son événement clic et placez-y le code qui ouvre une connection (variable globale) et prépare une requête :

const k_parametrized_update_request=
            'UPDATE invoice '
          + '  SET i_customer= ?'
          + '  WHERE (i_id= ?) '
          ;

var g_c_bdp_commandBdpCommandNil;

procedure TWinForm.prepare__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    g_c_bdp_command:= BdpCommand.Create(k_parametrized_update_requestBdpConnection1);
    BdpConnection1.Open();
    g_c_bdp_command.ParameterCount:= 2;
    g_c_bdp_command.Prepare();
  end// prepare__Click

   ajoutez un autre Button, appelez-le "execute_" et exécutez la requête dans sa méthode clic:

procedure TWinForm.execute__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_bdp_parameterBdpParameter;
  begin
    // -- create, add and initialize the parmeters
    l_c_bdp_parameter:= g_c_bdp_command.Parameters.Add('i_customer',
        BdpType.String);
    if l_c_bdp_parameterNil
      then display_bug_stop('not_added');
    l_c_bdp_parameter.Size:= 7;
    l_c_bdp_parameter.Value:= customer_text_box_.Text;

    l_c_bdp_parameter:= g_c_bdp_command.Parameters.Add('i_id',
        BdpType.Int32);
    l_c_bdp_parameter.Value:= Convert.ToInt32(id_text_box_.Text);

    g_c_bdp_command.ExecuteNonQuery();
  end// execute__Click

   compilez, exécutez, cliquez "list_", "prepare_", "execute_" et "list_"
   voici le résultat:

image



Au lieu de créer la commande et les paramètres par code, nous pouvons aussi utiliser le composant BdpCommand situé sur la Palette:
   sélectionnez la page "Borland Data Provider" de la Palette
   sélectionnez BdpCommand:

image

et posez-le sur la Forme

   sélectionnez la propriété Connection de BdpCommand1 et initialisez la à BdpConnection1
   ajoutez la requête en utilisant l'éditeur de commandes:
  • sélectionnez la propriété CommandText, et cliquez l'ellipse "...'
  • l'éditeur de commande est affiché:

    image

  • sélectionnez INVOICE, sélectionnez "Command Type | Update", cliquez "Generate SQL" et corrigez la requête pour qu'elle corresponde à celle présentée ci-dessus

    image

   créez et initialisez les paramètres:
  • sélectionnez la propriété Parameters de BdpCommand1, cliquez l'ellipse "..."
  • l'éditeur de paramètres est présenté:

    image

    Notez que tous les champs sont présentés

  • supprimez tous les champs sauf i_id et i_customer, et placez i_customer en tête
  • cliquez "Ok"
   ajoutez un Button qui appelle Prepare:

procedure TWinForm.prepare_2__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    BdpConnection1.Open();
    BdpCommand1.Prepare();
  end// prepare_2__Click

   ajoutez un autre Button, appelez-le "execute_2_" et exécutez la requête dans sa méthode clic:

procedure TWinForm.execute_2__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    BdpCommand1.Parameters[0].Value:= customer_text_box_.Text;
    BdpCommand1.Parameters[1].Value:= Convert.ToInt32(id_text_box_.Text);;

    BdpCommand1.ExecuteNonQuery();
  end// execute_2__Click

   compilez, exécutez, cliquez "list_", "prepare_", "execute_" et "list_"
   voici le résultat:

image



Nous aurons encore l'occasion de retrouver les requêtes paramétrées pour la mise à jour depuis des contrôles visuels.

Vous trouverez le code source dans p_23_bdp_update_table.zip




4.5 - Afficher des données

4.5.1 - SqlDataReader

Pour afficher le contenu d'une TABLE, nous devons récupérer les données du Serveur. Pour cela, nous utilisons:
  • un BdpConnection
  • une commande BdpCommand
  • un iDataReader qui nous est fourni par la commande de lecture
  • éventuellement des contrôles pour afficher
En rouge sur notre schéma d'architecture, le DataReader et une TextBox:

image

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

 
SELECT i_idi_customeri_date
  FROM invoice
  WHERE i_id< 102



Au niveau fonctionnement:

  • le Client envoie la requête de lecture en préparant un tampon pour recevoir les données de la réponse. Au niveau de la syntaxe ADO .Net
    • nous utilisons un BdpCommand
    • nous appelons la fonction ExecuteReader()
    • cette fonction retourne un objet SqlDataReader() qui sera utilisé pour lire les lignes
    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_31_bdp_display_data_reader"
   posez un Button sur la Forme, nommez-le "display_", créez sa méthode clic et tapez le code qui appelle ExecuteReader():

procedure TWinForm.display_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_commandBdpCommand;
      l_c_readeriDataReader;
      l_row_indexInteger;
      l_column_indexInteger;
      l_displayString;
  begin
    l_c_command:= BdpConnection1.CreateCommand();
    l_c_command.CommandText:= k_select_training;

    BdpConnection1.Open();
    l_c_reader:= l_c_command.ExecuteReader();

    l_row_index:= 0;
    while l_c_reader.Read() do
    begin
      l_display:= '';
      for l_column_index:= 0 to l_c_reader.FieldCount- 1 do
        l_display:= l_display' 'l_c_reader.GetValue(l_column_index).ToString;
      display(l_row_index.ToString':'l_display);
      Inc(l_row_index);
    end// while
    BdpConnection1.Close();
  end// display_invoice_click

   compilez et exécutez

   voilà le résultat:

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.5.2 - Affichage dans un DataGrid par code

Nous allons afficher les données de la table INVOICE dans un DataGrid.

Pour cela nous allons être obligés d'utiliser toute la batterie des composants ADO .Net:

  • BdpConnection, BdpCommand pour récupérer les données
  • DataAdapter pour pomper les données dans un DataSet en appelant Fill()
  • le DataSet qui contiendra un DataTable où seront stockées TOUTES les lignes
  • un DataGrid pour afficher
Soit, avec notre vue architecturale:

image

Au niveau code:

  • nous mettons les composants en place:
    • BdpCommand contient simplement le SELECT
    • un BdpDataAdapter est créé et notre BdpCommand lui est relié
    • un DataSet vide est créé
  • nous chargeons les données
    • en ouvrant la connexion
    • en appelant BdpDataAdapter.Fill()
    • nous pouvons fermer la connexion
  • nous relions la table unique du DataSet à une DataGrid


Par conséquent:
   créez un nouveau projet, et appelez-le "p_32_display_datagrid_code"
   dans l'Explorateur sélectionnez la connexion "order_entry" et tirez ma sur la Forme
   de l'onglet DataControls de la Palette, sélectionnez le DataGrid:

image

et posez sur la Forme

   posez un Button sur la Forme, nommez-le "data_table_", créez sa méthode clic et tapez le code qui appelle ExecuteReader():

const k_select_invoice'SELECT * FROM invoice';

procedure TWinForm.adapter__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_commandBdpCommand;
      l_c_data_adapterBdpDataAdapter;
      l_c_data_setDataset;
      l_c_data_table_invoice_refDataTable;
  begin
    l_c_command:= BdpCommand.Create(k_select_invoiceBdpConnection1);

    l_c_data_adapter:= BdpDataAdapter.Create;
    l_c_data_adapter.SelectCommand:= l_c_command;

    l_c_data_set:= DataSet.Create('my_invoices');

    BdpConnection1.Open();
    l_c_data_adapter.Fill(l_c_data_set);
    BdpConnection1.Close();

    l_c_data_table_invoice_ref:= l_c_data_set.Tables[0];

    // -- view a single table in the dbGrid
    DataGrid1.DataSource:= l_c_data_table_invoice_ref;
  end// adapter__Click

   compilez, exécutez et cliquez "adapter_"

   voilà le résultat:

image



Quelques commentaires:
  • tous les objets (sauf la BdpConnection) sont créés en local, ce qui permet de bien voir comment et quand ils sont utilisés
  • la connexion n'a besoin d'être ouverte que pour le chargement du DataSet. Une fois les données rapatriées en mémoire, la connexion peut être fermée
  • c'est FILL qui est l'instruction clé. Elle sera nécessaire, explicitement ici, ou implicitement via un Borland Data Adapter ailleurs.

    Notez aussi que, contrairement à dbExpress où le "sens" du chaînage des composants est uniforme:

        DataGrid -> tDataSource -> tClientDataset -> tDataProvider -> tSqlQuery -> tBdpConnection

    ici Fill() inverse le chaînage:

        DataGrid -> DataTable -> DataTable <== DataAdapter -> tBdpCommand -> tBdpConnection

  • si notre commande contenait plusieurs TABLES (SELECT multiples séparés par des ";") :

     
    SELECT * FROM invoice ; SELECT * FROM item

    alors le DataSet contiendrait plusieurs DataTable. Ici nous avons utilisé un SELECT sur une TABLE.

    Mentionnons que seul les providers liés à SQL Server permettent ces requêtes multiples

    Mais le fait important est qu'Ado .Net comporte un DataSet (un SET de DATA tables) et que cet élément est nouveau par rapport aux composants dbExpress

    Et ceci explique pourquoi nous avons du utiliser DataSet.Tables[0]

  • finalement nous relions le DataGrid à notre DataTable. Nous aurions aussi pu relier la DataGrid au DataSet:

    procedure TWinForm.dataset__Click(senderSystem.Object;
        eSystem.EventArgs);
      var l_c_commandBdpCommand;
          l_c_data_adapterBdpDataAdapter;
          l_c_data_setDataset;
      begin
        l_c_data_adapter:= BdpDataAdapter.Create;
        l_c_data_adapter.SelectCommand:= BdpCommand.Create(k_select_invoiceBdpConnection1);;

        l_c_data_set:= DataSet.Create('my_invoices');

        BdpConnection1.Open();
        l_c_data_adapter.Fill(l_c_data_set);
        BdpConnection1.Close();

        DataGrid1.DataSource:= l_c_data_set;
      end// dataset__Click

    avec le résultat que voici:

    image

    nous cliquons sur "+":

    image

    et enfin nous cliquons sur "Table":

    image

    Notez le titre "my_invoice" que nous avions fourni lors de la création du DataSet, ainsi que l'icône de navigation en haut à droite du DataGrid.

    Cette structure arborescente avec une seule TABLE est totalement sans intérêt ici, et c'est pourquoi nous avons relié la DataGrid à DataSet.Tables[0]



4.5.3 - Affichage par composants

Voici à présent comment effectuer le même traitement en utilisant les composants Bdp_xxx pris sur la Palette:
   créez un nouveau projet, et appelez-le "p_33_bdp_display_datagrid"
   dans l'Explorateur de données, sélectionnez la connexion "order_entry" et tirez la sur la Forme
   BdpConnection1 est créé et placé dans la zone des composants non-visuels
   dans l'onglet "Borland Data Provider" de la Palette sélectionnez un BdpDataAdapter:

image

tirez-le sur la Forme

   Delphi le dépose dans la zone des composants

   sélectionnez BdpDataAdapter1, sélectionnez sa propriété SelectCommand puis Connection et placez-y BdpConnection1
   sélectionnez BdpDataAdapter1, et sélectionnez le lien "Configure Data Adapter" situé au bas de l'Inspecteur d'Objet (ou, sur clic droit souris, sélectionnez "Configure Data Adapter")
   le Configurateur de BdpDataAdapter s'affiche:

image

   cliquez "Generate SQL"
   les requêtes sont affichées
   à titre de vérification, sélectionnez l'onglet "Preview Data" et "Refresh"
   une partie de la table est affichée!

image

   créez et liez un DataSet en sélectionnant l'onglet "DataSet", "New DataSet" et "Ok"

   un nouveau DataSet est placé dans la zone des composants non visuels

   chargez DataSet1 en basculant la propriété Active de BdpAdapter1 à True

   placez une DataGrid sur la Forme

   dans la propriété DataSource de DataGrid, sélectionnez Table1

   les données de la table sont affichées dans DataGrid1 PENDANT LA CONCEPTION:

image



Notez que
  • l'instruction BdpDataAdapter1.Fill() est invoquée pendant la conception, ce qui permet de visualiser les données réelles de la Table. Ceci ne fonctionne que pour le BdpDataAdapter (pas le SqlDataAdapter ou le FbDataAdapter)


4.5.4 - Relation Maître Détail

Nous allons réaliser, en utilisant les composants de la Palette, un affichage Maître Détail de 2 tables:
  • une première DataGrid affichera les factures
  • une seconde DataGrid affichera les articles de cette commande
   créez un nouveau projet et nommez-le "p_34_bdp_master_detail"
   dans l'Explorateur de données, sélectionnez la connexion "order_entry" et tirez la sur la Forme
   BdpConnection1 est créé et placé dans la zone des composants non-visuels

   placez l'ensemble des composants pour la table Maître:
  • posez un BdpDataAdapter
  • initialisez BdpAdapter1.SelectCommand.Connection vers BdpConnection1
  • sélectionnez "Configure Data Adapter"
    • sélectionnez INVOICE
    • cliquez "Generate SQL"
    • dans l'onglet "DataSet", cliquez "New DataSet" et "Ok"
  • basculez BdpAdapter1.Active sur True
  • déposez une DataGrid, initialisez sa propriété DataSource vers DataTable1
   placez les composants pour al table Détail
  • posez un BdpDataAdapter
  • initialisez BdpAdapter2.SelectCommand.Connection vers BdpConnection1
  • sélectionnez "Configure Data Adapter"
    • sélectionnez ITEM
    • cliquez "Generate SQL"
    • dans l'onglet "DataSet", cliquez "Existing Dataset" et dans la combobox, DataSet1. ITEM utilise donc le même DataSet que INVOICE
    • puis cliquez "Ok"
  • basculez BdpAdapter2.Active sur True
   créez une relation qui lie les deux tables:
  • sélectionnez DataSet1
  • pour vérification, en cliquant l'ellipse après Tables, les deux tables sont affichées:

    image

    Fermez le dialogue

  • sélectionnez Relations et cliquez l'ellipse "..."
  • l'éditeur de relations s'affiche

    image

  • cliquez "Add"

  • l'éditeur de lien s'affiche

    image

  • dans "key column", sélectionnez i_id, et dans "Foreign Key Column", sélectionnez it_invoice_ref

    Cliquez "Ok", puis "Close"

   sélectionnez DataGrid2
Dans DataSource, placez DataSet1, et dans DataMember "invoice.Relation1"

   vérifiez que les deux BdpDataProvider ont bien Active à True

   voici la Forme à la conception:

image



Notez que:
  • le projet contient aussi un exemple de jointure SQL des deux tables, et un bouton qui permet de calculer la relation par code



4.6 - DataSet en mémoire

4.6.1 - Architecture du DataSet

Nous allons nous pencher sur les traitements effectués au niveau du DataSet: remplir, modifier, trier etc. Nous pourrions utiliser un DataSet connecté et rempli par un DataAdapter, comme nous l'avons fait ci-dessus. Il est plus instructif d'utiliser un DataSet autonome, que nous construirons et manipulerons par code uniquement. Mais souvenez-vous que tous les traitements pourraient être réalisés sur un DataSet rempli depuis des TABLEs du Serveur.

Nous sommes donc au niveau du DataSet:

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.6.2 - Création d'une DataTable par code

Nous nous créer une DataTable avec des factures (identificateur, nom). Il suffit, après la création de la DataTable de créer chaque colonne, en spécifiant son type avec éventuellement des attributs.

Voici comment créer la table en mémoire
   créez un nouveau projet, et appelez-le "p_41_bdp_create_in_memory"
   posez un Button sur la Forme, nommez-le "create_data_table_", créez sa méthode clic et tapez le code qui créé la DataTable:

var g_c_invoice_data_tableDataTable;

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

et écrivez le code d'affichage (f_display_datarow() affiche le contenu d'une ligne et est placée dans une unité d'utilitaires qui est contenue dans le .ZIP)

procedure TWinForm.row_changed(sendertObject;
    eSystem.Data.DataRowChangeEventArgs);
  var l_action_nameSystem.String;
  begin
    display(System.String.Format('on_row_changed {0} 'e.Action)
       + ' : ' + f_display_data_row(e.Row, 2))
  end// row_changed

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 DataSet 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 Button 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 Button, "display_", et écrivez le code qui affiche le contenu de la DataTable :

procedure TWinForm.display__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_row_enumeratoriEnumerator;
      l_c_data_rowDataRow;
      l_displaySystem.String;
  begin
    l_c_row_enumerator:= g_c_invoice_data_table.Rows.GetEnumerator();

    while l_c_row_enumerator.MoveNext() do
    begin
      l_c_data_row:= l_c_row_enumerator.Current as DataRow;
      l_display:= f_display_data_row(l_c_data_row,
          g_c_invoice_data_table.Columns.Count);
      // -- there is also a ToString property
      display(l_display);
    end// while l_c_row_enumerator
  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:

    my_value:= System.String.Format('proposed value {0} 'e.ProposedValue);



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 Button, "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;
  end// update__Click

   posez un autre Button, "accept_changes_", et écrivez le code qui valide et confirme les modifications:

procedure TWinForm.accept_changes__Click(senderSystem.Object;
    eSystem.EventArgs);
    // -- implicitly calls EndEdit, which triggers validations if any
  begin
    display_line;
    g_c_invoice_data_table.AcceptChanges;
  end// accept_changes__Click

   posez un autre Button, "display_all", et écrivez le code qui affiche le contenu de la DataTable et l'état de la ligne:

function f_display_data_row_state_version(p_c_data_rowDataRow;
    p_column_countInteger): System.String;
  var l_column_indexInteger;
      l_current_valuel_proposed_valuel_valueSystem.String;
  begin
    Result:= System.String.Format('{0} 'p_c_data_row.RowState);

    for l_column_index:= 0 to p_column_count- 1 do
    begin
      if p_c_data_row.HasVersion(DataRowVersion.Current)
        then l_current_value:= p_c_data_row[l_column_index,
            DataRowVersion.Current].ToString()
        else l_current_value:= '-';

      if p_c_data_row.HasVersion(DataRowVersion.Proposed)
        then l_proposed_value:= p_c_data_row[l_column_index,
            DataRowVersion.Proposed].ToString()
        else l_proposed_value:= '-';

      l_value:= l_current_value;
      if (l_current_value<> l_proposed_value)
        then l_value:= l_value' [=> 'l_proposed_value']';

      Result:= Result' 'l_value;
    end// vor l_column_index
  end// f_display_data_row_state_version

procedure TWinForm.display_all__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_row_enumeratoriEnumerator;
      l_c_data_rowDataRow;
      l_displaySystem.String;
      l_data_row_stateDataRowState;
      l_stateString;
      l_c_row_versionDataRow;
  begin
    l_c_row_enumerator:= g_c_invoice_data_table.Rows.GetEnumerator();

    while l_c_row_enumerator.MoveNext() do
    begin
      l_c_data_row:= l_c_row_enumerator.Current as DataRow;

      display(f_display_data_row_state_version(l_c_data_row, 2));
    end// while l_c_row_enumerator
  end// display_all__Click

   compilez exécutez. Cliquez "display_state_", "begin_edit_", "update_", "display_state_"

   voilà le résultat:

image



Nous avons aussi ajouté la possibilité d'effacer des lignes. Le tout se trouve dans le fichier .ZIP 41_bdp_create_in_memory.zip téléchargeable



4.6.3 - Les Vues

Les vues sont des représentations mémoire de la table comportant des modifications telles que le tri, le filtrage etc.

ADO.Net utilise donc un composant séparé pour effectuer les traitements sur les données brutes d'une DataTable. Et chaque DataSet comporte une collection de telles vues:

image



A chaque table est attachée une vue par défaut, que nous pouvons récupérer en utilisant la propriété ma_table.DefaultView.

my_c_data_view:= my_c_data_table.DefaultView;

Nous pouvons trier une DataView en invoquant

my_c_data_view.Sort:= 'i_customer ASC';



Voici le code:
   créez un nouveau projet, et appelez-le "p_42_bdp_data_view"
   posez un Button sur la Forme, nommez-le "create_data_set_", créez sa méthode clic et tapez le code qui créera une DataSet et le remplira (comme dans l'exemple ci-dessus). Nous avons utilisé une table à 3 colonnes (i_id, i_customer et i_amount), et l'avons rempli avec:

var g_c_data_setDataSet;

procedure TWinForm.create_data_set__Click(senderSystem.ObjecteSystem.EventArgs);
  var l_c_invoice_data_tableDataTable;

  procedure create_column_definitions;
    var l_c_data_columnDataColumn;
    begin
      l_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:= 15;
      l_c_invoice_data_table.Columns.Add(l_c_data_column);

      l_c_invoice_data_table.Columns.Add('i_amount'TypeOf(Double));
    end// create_column_definition

  procedure insert_row_values;

    procedure add_invoice(p_idIntegerp_customerSystem.Stringp_amountDouble);
      var l_c_data_rowDataRow;
      begin
        l_c_data_row:= l_c_invoice_data_table.NewRow();

        l_c_data_row['i_id']:= p_id;
        l_c_data_row['i_customer']:= p_customer;
        l_c_data_row['i_amount']:= p_amount;

        l_c_invoice_data_table.Rows.Add(l_c_data_row);
      end// add_invoice

    begin // insert_row_values
      add_invoice(201, 'apple',   1234.51);
      add_invoice(202, 'exxon',   625.51);
      add_invoice(203, 'dow',     334.51);
      add_invoice(204, 'ibm',     134.51);
    end// insert_row_values

  var l_c_data_viewDataView;

  begin // create_data_set__Click
    g_c_data_set:= DataSet.Create('business');

    l_c_invoice_data_table:= DataTable.Create();
    l_c_invoice_data_table:= g_c_data_set.Tables.Add('invoice');

    create_column_definitions;

    insert_row_values;

    l_c_data_view:= DataView.Create(l_c_invoice_data_table);
    display_data_view(l_c_data_view, 3);
  end// create_data_set__Click

   posez un Button sur la Forme, nommez-le "sort_", créez sa méthode clic et ajoutez une expression de tri à la vue par défaut:

procedure TWinForm.sort__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_sorted_data_viewDataView;
  begin
    l_c_sorted_data_view:= g_c_data_set.Tables[0].DefaultView;
    l_c_sorted_data_view.Sort:= 'i_customer ASC';
    display_data_view(l_c_sorted_data_view, 3);
  end// sort__Click

   compilez, exécutez, créez la DataTable, et cliquez "sort_"

   voici le résultat:

image



Encore une fonction utile sur les vues: la recherche. Elle est effectuée par la fonction Find() et retourne la ligne correspondant à la recherche:

my_c_table.DefaultView.Sort:= 'i_customer ASC';
my_find_index:= y_c_table.DefaultView.Find('ibm');

Soulignons que:

  • il est OBLIGATOIRE de trier avant d'effectuer la recherche. Une exception est d'ailleurs provoquée si ce n'est pas le cas
  • si vous ne souhaitez pas perturber l'affichage, utilisez une table annexe, puis positionnez vous à la ligne trouvée
Donc:
  • posez une TextBox sur la Forme
  • posez un Button sur la Forme, nommez-le "sort_", créez sa méthode clic et ajoutez une expression de tri et recherche dans la vue par défaut:

    procedure TWinForm.find__Click(senderSystem.Object;
        eSystem.EventArgs);
      var l_c_sorted_data_viewDataView;
          l_string_to_findSystem.String;
          l_find_indexInteger;
      begin
        l_string_to_find:= find_text_box_.Text;

        // -- MUST sort
        l_c_sorted_data_view:= g_c_data_set.Tables[0].DefaultView;
        l_c_sorted_data_view.Sort:= 'i_customer ASC';

        display_data_view(l_c_sorted_data_view, 3);

        l_find_index:= l_c_sorted_data_view.Find(l_string_to_find);
        display('found at 'l_find_index.ToString);
      end// find__Click

  • compilez, exécutez, créez la DataTable, tapez une valeur à chercher et cliquez "find_"

  • voici le résultat:

    image



Nous pouvons aussi supprimer des lignes de notre vue en écrivant une expression de filtrage dans la propriété RowFilter:

my_c_data_table.DefaultView.RowFilter:= ' i_customer> ''g'' ';

Et voici le code:
   posez un Button sur la Forme, nommez-le "filter_", créez sa méthode clic et ajoutez une expression de filtrage à la vue par défaut:

procedure TWinForm.filter__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_filtered_data_viewDataView;
  begin
    l_c_filtered_data_view:= g_c_data_set.Tables[0].DefaultView;
    l_c_filtered_data_view.RowFilter:= ' i_customer> ''g'' ';
    display_data_view(l_c_filtered_data_view, 3);
  end// filter__Click

Nous avons, naturellement, écrit un procédure à laquelle nous passons un DataView et qui affiche son contenu. Elle est contenu dans le .ZIP

   compilez, exécutez, cliquez "create" et "filter"

   voici le résultat

image



Notez que

  • si vous exécutez plusieurs traitements dans la foulées, il faut recliquer "create_" pour avoir une table de plusieurs lignes à trier. En effet, nos DataView sont des pointeurs vers la DefaultDataView, et si nous la filtrons nous récupérons une vue d'une ligne (dans notre cas) et il n'y a plus grand chose à trier.


Il existe aussi la possibilité de lancer des requêtes SQL sur la DataSet mémoire (agrégats, jointure), mais nous ne présenterons pas ces traitements ici.



4.7 - Les DataBindings

4.7.1 - Principe des DataBindings

Lorsque nous relions des contrôles visuels à nos données d'un DataSet, nous les connectons toujours à une DataView. En fait, lorsque nous écrivons:

my_data_grid.DataSource:= my_data_table;

c'est la mécanique polymorphique de Ado.Net qui se met en route pour traduire cela en:

my_data_grid.DataSource:= my_data_table.DefaultDataView;



Tout se passe bien pour l'affichage, mais les modifications dans les contrôles visuels ne sont pas transféreés dans les données en mémoire. Pour cela, il faut utiliser des DataBindings qui assurent les communications entre les contrôles visuels et les données des DataSets:

image

Ces DataBindings sont assez délicats à manier, et j'ai trouvé sur Google plusieurs messages qui étaient beaucoup plus sévères que moi à leur encontre.

Le but fondamental des DataBindings est de pouvoir définir des "zones de déplacement synchrones", pour lesquelles tous les contrôles reliés à une zone notifient et sont notifiés des déplacements dans le DataView sous-jacent.

Les DataBindings sont obtenus via des BindingContexts. Ces contextes sont associés au CONTROLES VISUELS. Il y a ainsi un BindingContext associé à la Forme, ainsi qu'à des DataGrids, des TextBox etc. Nous utiliserons celui de la Forme pour rester simple.

Un BindingContext nous permet de récupérer un CurrencyManager, où "currency" signifie "courant".

Le schémma MSDN classique est le suivant:

image

Et ces DataBindings peuvent être utiliser pour synchroniser

  • de nombreux contrôles tels que les DataGrids, ListBox, TextBox
  • avec de nombreuses sources de données: DataView, bien sûr, mais aussi avec des données classiques (un Integer, un ARRAY OF ...).
Les DataBindings sont de deux types:
  • ceux qui synchronisent des données simples (un Integer)
  • ceux qui synchronisent des listes de données (les valeurs d'une colonne d'un DataTable, les données d'un ARRAY, d'une Collection). En fait tout ce qui implémente l'interface iList.

4.7.2 - DataBindings simples d'un DataTable

Pour ajouter une liaison à un contrôle visuel, nous appelons simplement

 
my_visual_control.DataBindings.Add(my_property, my_view, my_expression);

  • my_property indique quelle partie visuelle est liée. Par exemple 'Text' pour une TextBox
  • my_view spécifie d'où viennent les données: par exemple un DataSet ou un ARRAY
  • my_expression précise quelle partie de la source est visualisée. Par exemple la colonne 'customer' d'un DataSet
Une fois la liaison établie, nous créons en général un CurrencyManager qui a la propriété fondamentale Position. En modifiant la valeur de Position (par code ou par boutons), nous nous déplacerons dans nos données.



Voici un premier exemple avec deux TextBox et une DataTable
   créez un nouveau projet, et appelez-le "p_43_bdp_simple_data_binding"

   déclarez un DataSet dans la partie PUBLIC de la Forme

type
  TWinForm = class(System.Windows.Forms.Form)
    // -- ...
    public
      m_c_data_setDataSet;
      constructor Create;
    end// TWinForm

   posez un Button sur la Forme, nommez-le "create_data_table_", créez sa méthode clic et tapez le code qui créera une DataSet

procedure TWinForm.create_data_table__Click(senderSystem.Object
    eSystem.EventArgs);
  var l_c_invoice_data_tableDataTable;

  procedure create_column_definitions;
    var l_c_data_columnDataColumn;
    begin
      l_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:= 15;
      l_c_invoice_data_table.Columns.Add(l_c_data_column);

      l_c_invoice_data_table.Columns.Add('i_amount'TypeOf(Double));
    end// create_column_definition

  procedure insert_row_values;

    procedure add_invoice(p_idIntegerp_customerSystem.String
        p_amountDouble);
      var l_c_data_rowDataRow;
      begin
        l_c_data_row:= l_c_invoice_data_table.NewRow();

        l_c_data_row['i_id']:= p_id;
        l_c_data_row['i_customer']:= p_customer;
        l_c_data_row['i_amount']:= p_amount;

        l_c_invoice_data_table.Rows.Add(l_c_data_row);
      end// add_invoice

    begin // insert_row_values
      add_invoice(201, 'macy',   1234.51);
      add_invoice(202, 'exxon',   625.51);
      add_invoice(203, 'dow',     334.51);
      add_invoice(204, 'ibm',     134.51);
    end// insert_row_values

  begin // create_data_set__Click
    m_c_data_set:= DataSet.Create('business');

    l_c_invoice_data_table:= DataTable.Create();
    l_c_invoice_data_table:= m_c_data_set.Tables.Add('invoice');

    create_column_definitions;
    insert_row_values;
  end// create_data_set__Click

et remplissez le DataTable (comme dans l'exemple ci-dessus). Nous avons utilisé une table à 3 colonnes (i_id, i_customer et i_amount), et l'avons rempli avec les mêmes valeurs que ci-dessus

   posez une TextBox pour afficher la colonne 'i_id' et appelez-le id_text_box
   posez une TextBox pour afficher la colonne 'i_customer' et appelez-le customer_text_box

   posez un Button sur la Forme, nommez-le "bind_data_table_", créez sa méthode clic et ajoutez le code qui va ajouter un DataBinding à chaque Contrôle et créera le CurrencyManager :

procedure TWinForm.bind_data_table__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_bindingBinding;
  begin
    l_c_binding:= Binding.Create('Text',
        m_c_data_set'INVOICE.I_ID');
    id_text_box_.DataBindings.Add(l_c_binding);

    customer_text_box_.DataBindings.Add('Text',
        m_c_data_set'invoice.i_customer');

    g_c_currency_manager:=
        BindingContext[m_c_data_set.Tables[0].DefaultViewas CurrencyManager;

    Include(g_c_currency_manager.CurrentChangedcurrent_changed);
    Include(g_c_currency_manager.PositionChangedposition_changed);
  end// bind_data_table__Click

   posez un Button sur la Forme, nommez-le "next_", créez sa méthode clic et modifiez la position du CurrencyManager:

procedure TWinForm.next__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    BindingContext[m_c_data_set'invoice'].Position:=
        BindingContext[m_c_data_set'invoice'].Position+ 1;
  end// next__Click

   faites de même avec "previous_"

   compilez, exécutez, créez la DataSet, éventuellement affichez son contenu, cliquez "bind_", puis "next_"

   voici le résultat

image



Notez que:

  • la liaison est effectuée en précisant "INVOICE.I_ID" : la DataTable et la colonne


Faisons de même avec un ARRAY. Les tableaux dynamiques implémentent bien en .Net l'INTERFACE iList.

Nous sommes donc dans le cas:

image

Nous allons créer un tableau d'objets facture:

  • voici la définition de la CLASS

    type c_invoice=
      class
         Public
           m_idInteger;
           m_customerSystem.String;
           m_amountDouble;

           Constructor create_invoice(p_idInteger;
               p_customerSystem.Stringp_amountDouble);
           procedure display_invoice;

           property id : Integer read m_id write m_id;
           property customer : System.String
               read m_customer write m_customer;
           property amount : Double
               read m_amount write m_amount;
       end// c_invoice

  • le tableau est défini par:

    g_c_invoice_arrayarray of c_invoice;



Et voici le code:
   créez une UNIT u_c_invoice, définissez-y la CLASS c_invoice et écrivez son CONSTRUCTOR et display_invoice

   importez cette unité dans le USES de l'unité principale

   déclarez le tableau dans la partie PUBLIC de la Forme

   posez un Button sur la Forme, nommez-le "create_array_", créez sa méthode clic et tapez le code qui créera le tableau et le remplira:

procedure TWinForm.create_array__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_invoice_indexInteger;

  procedure add_invoice(p_idIntegerp_customerSystem.String;
      p_amountDouble);
    var l_c_invoicec_invoice;
    begin
      l_c_invoice:= c_invoice.create_invoice(p_idp_customerp_amount);

      g_c_invoice_array[l_invoice_index]:= l_c_invoice;
      Inc(l_invoice_index);
    end// add_invoice

  begin // create_array__Click
    SetLength(g_c_invoice_array, 4);

    l_invoice_index:= 0;
    add_invoice(201, 'macy',   1234.51);
    add_invoice(202, 'exxon',   625.51);
    add_invoice(203, 'dow',     334.51);
    add_invoice(204, 'ibm',     134.51);
  end// create_array__Click

   posez une TextBox pour afficher la PROPERTY id et appelez-la array_id_text_box
   posez une TextBox pour afficher la PROPERTY customer et appelez-la array_customer_text_box

   posez un Button sur la Forme, nommez-le "bind_array_", créez sa méthode clic et ajoutez le code qui va ajouter un DataBinding à chaque Contrôle et créera le CurrencyManager :

procedure TWinForm.bind_array__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    array_id_text_box_.DataBindings.Add('Text'g_c_invoice_array'id');
    array_customer_text_box_.DataBindings.Add('Text',
        g_c_invoice_array'customer');
  end// bind_array__Click

   posez un Button sur la Forme, nommez-le "next_t_", créez sa méthode clic et modifiez la position du CurrencyManager:

procedure TWinForm.next_t__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    BindingContext[g_c_invoice_array].Position:=
        BindingContext[g_c_invoice_array].Position+ 1;
  end// next_t__Click

   faites de même avec "previous_t_"

   compilez, exécutez, créez le tableau,, éventuellement affichez son contenu, cliquez "bind_", puis "next_"

   voici le résultat

image



Quelques remarques:
  • nous avons placé les déclarations des données dans la Forme. J'imagine que cela facilite les communications avec le ContextManager
  • le CurrencyManager n'est utile que pour avancer dans les données par la manipulation de Position
  • pour le tableau, les données qui sont affichées doivent être dans des parties PUBLIC de c_invoice, et nous les avons même placées sous formes de PROPERTYes
  • le résultat précédent n'est guère impressionnant. 8 heures de bataille pour trouver les bons composants à connecter. Comme .Net est "hyper articulé" (morcellement de chaque objet) et "hyper polymorphique" (entre "()" ou "[]" vous pouvez placez quasiment n'importe quoi, le compilateur accepte, et l'exécution vous demande de rallumer le PC), les tâtonnements ont été numbreux.

    Ce qui nous a sorti de l'ornière ce sont les événements CurrentChanged et PositionChanged avec test pas à pas par AS de à qui nous avions affaire dans les paramètres de l'événement. Vous trouverez le code des événements dans les .ZIP.

    Alors est-ce que les DataBindings valent tous ces efforts ? A mon sens oui, car ils sont le point obligé pour la synchronisation entre les contrôles visuels et les données (mémoire, puis sur le Serveur).



4.7.3 - DataBinding multiple

Dans notre article Firebird et ADO .Net, nous avons aussi présenté comment réaliser des DataBindings multiples




4.8 - Modification de données du Serveur

4.8.1 - Principe de Update

Pour modifier des données du Serveur, nous procédons en deux étapes
  • modification des données de la DataTable en mémoire
  • appel de DataAdapter.Update
Lorsque DataAdapter.Update est invoqué, des instruction SQL qui ajoutent, modifient ou effacent des données sont exécutées. Ces instructions proviennent de deux origines
  • soit le DataAdapter se débrouille pour déduire les INSERT, UPDATE et DELETE de la requête SELECT qui a été utilisée pour charger le DataTable
  • soit nous écrivons les instructions nous-même


4.8.2 - Modification par code

Pour modifier les données par code uniquement, voici comment procéder:
   créez un nouveau projet, et appelez-le "p_51_bdp_update_dataset_code"
   posez un Button sur la Forme, nommez-le "change_training_", créez sa méthode clic et tapez le code qui va créer un DataSet, le charger depuis le Serveur, et effectuer quelques modification des données chargées du Serveur:

procedure TWinForm.change_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_invoice_data_tableDataTable;
      l_c_data_rowDataRow;
  begin
    g_c_data_adapter:= SqlDataAdapter.Create(k_select_invoiceSqlConnection1);
    g_c_data_set:= DataSet.Create('my_dataset');
    g_c_data_adapter.TableMappings.Add('Table''my_invoice');
    g_c_data_adapter.Fill(g_c_data_set);
    l_c_invoice_data_table:= g_c_data_set.Tables['my_invoice'];

    display_data_table(l_c_invoice_data_table);

    // -- insert a row
    l_c_data_row:= l_c_invoice_data_table.NewRow();
    l_c_data_row['i_id']:= Convert.ToInt32(id_textbox_.Text);
    l_c_data_row['i_customer']:= customer_textbox_.Text;

    l_c_invoice_data_table.Rows.Add(l_c_data_row);

    // -- modify a row
    l_c_data_row:= l_c_invoice_data_table.Rows[3];
    l_c_data_row['i_customer']:= customer_textbox_.Text;

    // -- delete a row
    l_c_invoice_data_table.Rows[Convert.ToInt32(delete_id_textbox_.Text)].Delete;
  end// change_invoice__Click




Pour utiliser la modification automatique, il suffit d'appeler DataAdapter.Update(). Mais pour connaître le SQL utilisé par le DataAdapter, nous utilisons un CommandBuilder, et lui demandons d'afficher les commandes qui ont été synthétisée par le DataAdapter.

Cela se passe ainsi:
   posez un Button sur la Forme, nommez-le "update_command_builder_" et tapez le code suivant dans son clic:

procedure TWinForm.update_commandbuilder__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_command_builderBdpCommandBuilder;
  begin
    BdpConnection1.Open;
    l_c_command_builder:= BdpCommandBuilder.Create(g_c_data_adapter);
    g_c_data_adapter.Update(g_c_data_set);
    display(l_c_command_builder.GetUpdateCommand.CommandText);
    BdpConnection1.Close;
  end// update_commandbuilder__Click

   compilez et exécutez. Cliquez "change_invoice_" et "update_commandbuilder_"

   voici l'affichage de la requête de modification:

image



La requête est plutôt complexe, mais fondamentalement il s'agit de tester la valeur précédente d'un enregistrement en évitant de comparer à des valeurs NULL.



4.8.3 - Ecriture de SQL manuel

Si nous savons que seules certaines colonnes ont été modifiées, il est plus efficace de générer notre propre SQL.

Le principe est le suivant:

  • nous examinons chaque ligne de la DataTable, et testons sa valeur State: si elle a été modifiée, nous appelons une procédure qui va exécuter la requête SQL adaptée
  • pour INSERT, c'est très simple, nous remplissons simplement VALUES avec les valeur de la ligne
  • pour UPDATE nous butons, comme toujours, sur le problème des accès concurrents:
    • nous devons dans le WHERE indiquer quelle ligne du Serveur nous souhaitons modifier
    • le DataSet mémoire contient les valeur originales, et nous trouvons dans les valeurs originales la valeur que la clé de la ligne avait au moment du SELECT
    • nous pouvons aussi, si le traitement l'exige, vérifier que d'autres valeurs n'ont pas changé. Pour une réservation d'avion la clé (le numéro du vol) est utilisée, mais il est vraissemblable que nous nous assurons avant de réserver des places qu'un autre voyagiste n'a pas raflé toutes les places entre le moment ou nous avons lu par SELECT les informations sur le vol et le moment ou le client se décide à placer sa commande. Notre UPDATE incluera donc dans WHERE la comparaison du nombre de places.


Voici donc comment effectuer la modification par SQL manuel
   posez un Button sur la Forme, nommez-le "update_manual_sql_" et tapez le code suivant dans son clic:

procedure TWinForm.update_manual_sql__Click(senderSystem.Object;
    eSystem.EventArgs);

  var l_c_row_enumeratoriEnumerator;
      l_c_rowDataRow;

  begin // submit_updates
    SqlConnection1.Open();
    l_c_row_enumerator:= g_c_data_set.Tables[0].Rows.GetEnumerator();

    while l_c_row_enumerator.MoveNext() do
    begin
      l_c_row:= l_c_row_enumerator.Current as DataRow;
      case l_c_row.RowState of
        DataRowState.Added : do_insert_row(l_c_row);
        DataRowState.Modified : do_update_row(l_c_row);
      end// case
    end// while l_c_row_enumerator
    SqlConnection1.Close();
  end// update_manual_sql__Click

Pour INSERT nous avons opté pour une construction directe de la requête:

procedure do_insert_row(p_c_added_rowDataRow);
  var l_valuesl_requestSystem.String;
  var l_c_commandSqlCommand;
  begin
    l_values:= p_c_added_row['i_id'].ToString
      + ', '''p_c_added_row['i_customer'].ToString''''
      + ', '''p_c_added_row['i_date'].ToString''''
         ;
    l_request:= 'INSERT INTO invoice '
      +  ' (i_id, i_customer, i_date) 'k_new_line
      +  '    VALUES ('l_values')';

    l_c_command:= SqlCommand.Create(l_requestSqlConnection1);
    l_c_command.ExecuteNonQuery;
  end// do_insert_row

Et pour UPDATE nous avons utilisé une requête paramétrée:

procedure do_update_row(p_c_modified_rowDataRow);
  const k_update_invoice=
          'UPDATE invoice '
        + '  SET i_customer= ?'
        + '  WHERE i_id= ?';
  var l_c_bdp_commandBdpCommand;
      l_c_bdp_parameterBdpParameter;
  begin
    l_c_bdp_command:= BdpCommand.Create(k_update_invoiceBdpConnection1);
    l_c_bdp_parameter:= l_c_bdp_command.Parameters.Add('i_customer',
        BdpType.String);
    if l_c_bdp_parameterNil
      then display_bug_stop('not_added');
    l_c_bdp_parameter.Size:= 7;
    l_c_bdp_parameter.Value:= customer_textbox_.Text;

    l_c_bdp_parameter:= l_c_bdp_command.Parameters.Add('i_id',
        BdpType.Int32);
    l_c_bdp_parameter.Value:= Convert.ToInt32(id_textbox_.Text);

    l_c_bdp_command.ExecuteNonQuery;
  end// do_update_row

   compilez et exécutez


4.8.4 - Modification par des composant

Nous pouvons utiliser des composants de la Palette pour effectuer les traitements précédents.

Voici le projet:
   créez un nouveau projet, et appelez-le "p_52_bdp_update_dataset_via_data_grid"
   dans l'Explorateur de données, sélectionnez la connexion "order_entry" et tirez la sur la Forme
   BdpConnection1 est créé et placé dans la zone des composants non-visuels
   placez l'ensemble des composants pour afficher INVOICE dans une DataGrid:
  • posez un BdpDataAdapter
  • initialisez BdpAdapter1.SelectCommand.Connection vers BdpConnection1
  • sélectionnez "Configure Data Adapter"
    • sélectionnez INVOICE
    • cliquez "Generate SQL"
    • dans l'onglet "DataSet", cliquez "New DataSet" et "Ok"
  • basculez BdpAdapter1.Active sur True
  • déposez une DataGrid, initialisez sa propriété DataSource vers DataTable1
   pour modifier via un BdpCommandBuilder, placez un BdpCommandBuilder sur la Forme. Liez-le à BdpDataAdapter
   placez un Button qui appelle DataAdapter1.Update()
   placez un Button qui affichera la requête utilisée:

procedure TWinForm.command_builder__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_command_builderBdpCommandBuilder;
  begin
    BdpConnection1.Open();

    l_c_command_builder:= BdpCommandBuilder.Create(BdpDataAdapter1);
    BdpDataAdapter1.Update(DataSet1);

    display(l_c_command_builder.GetUpdateCommand.CommandText);
    BdpConnection1.Close();
  end// command_builder__Click

   compilez et exécutez
   voici le résultat:

image



La modification manuelle est réalisée comme pour la modification via le code.




5 - Télécharger le code source

Vous pouvez télécharger:

Ce .ZIP qui comprend:

  • le .DPR, la forme principale, les formes annexes eventuelles
  • les fichiers de paramètres (le schéma et le batch de création)
  • dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent:
  • créez un répertoire n'importe où sur votre machine
  • placez le .ZIP dans ce répertoire
  • dézippez et les sous-répertoires nécessaires seront créés
  • compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.

La notation utilisée est la notation alsacienne qui consiste à préfixer les identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse. Elle est présentée plus en détail dans l'article La Notation Alsacienne



Comme d'habitude:

  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou non) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :






6 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Created: jan-04. Last updated: mar-2020 - 250 articles, 620 .ZIP sources, 3303 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 - 2020
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
      + sql_server
        – msde_installation
        – ado_net_bdp
      + firebird
      + mysql
      + xml
      – paradox_via_ado
      – mastapp
      – delphi_business_objects
      – clientdataset_xml
      – data_extractor
      – rave_report_tutorial
      – visual_livebindings
      – migration_bde
    + web_internet_sockets
    + services_web_
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
    + admin
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Audit Delphi Bilan technique (technologie, méthodes, architecture, organisation, techologie et version, formation, gestion de projet) et recommandations - Tél 01.42.83.69.36
Formation Delphi xe3 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 Objet Delphi La programmation objet: les types, l'encapsulation, l'héritage, le polymorphisme - 3 jours
Formation Tcp/Ip Delphi La programmation sockets avec Delphi : principes, UDP, TCP, protocoles standards et sur mesure - 2 jours