IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Lazarus Pascal Discussion :

Organisation de code pour charger des données depuis DB


Sujet :

Lazarus Pascal

  1. #1
    Invité
    Invité(e)
    Par défaut Organisation de code pour charger des données depuis DB
    Bonjour,

    Je cherche actuellement comment organiser proprement mon code pour intéragir avec ma base de données.
    Pour cela je suis parti sur 2 objets simples, mais je prévois d'avoir par la suite des objets beaucoup plus complexes.
    J'ai les 2 objets suivants :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
     
      type
        TChange = class(TObjectFromDB)
        strict private
          FDeviseRef: TDevise;
          FDevise   : TDevise;
          FDebut    : TDateTime;
          FRate     : Extended;
        strict protected
          function GetTableNom: String; override;
          procedure LoadQuery(qry: TSQLQuery); override;
        public
          constructor Create;
          destructor Destroy; override;
          property DeviseRef: TDevise read FDeviseRef;
          property Devise   : TDevise read FDevise;
          property Debut    : TDateTime read FDebut;
          property Rate     : Extended read FRate;
        end;
     
    implementation
     
    { TChange }
     
    constructor TChange.Create;
    begin
      FDevise    := nil;
      FDeviseRef := nil;
    end;
     
    destructor tchange.destroy;
    begin
      if (Assigned(FDeviseRef)) then FreeAndNil(FDeviseRef);
      if (Assigned(FDevise))    then FreeAndNil(FDevise);
    end;
     
    function TChange.GetTableNom: String;
    begin
      Result := TBL_CHANGES;
    end;
     
    procedure TChange.LoadQuery(qry: TSQLQuery);
    begin
      FId        := qry.FieldByName('id'   ).AsInteger;
      FNom       := qry.FieldByName('nom'  ).AsString;
      FDebut     := qry.FieldByName('since').AsDateTime;
      FRate      := qry.FieldByName('taux' ).AsFloat;
     
      FDeviseRef := TDevise.Create;
      FDevise    := TDevise.Create;
      FDeviseRef.LoadFor(qry.FieldByName('devise_ref').AsInteger);
      FDevise.LoadFor(qry.FieldByName('devise').AsInteger);
    end;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
      type
        TDevise = class(TObjectCodeFromDB)
        private
          FSymbole  : String;
          FNbDecimal: Integer;
        protected
          procedure LoadQuery(qry: TSQLQuery); override;
        function GetTableNom: String; override;
        public
          property Symbole    : String  read FSymbole;
          property NbDecimales: Integer read FNbDecimal;
        end;
     
    implementation
     
    uses
      datas;
     
    { TDevise }
     
    function TDevise.GetTableNom: String;
    begin
      Result := TBL_DEVISES;
    end;
     
    procedure TDevise.LoadQuery(qry: TSQLQuery);
    begin
      FId        := qry.FieldByName('id'          ).AsInteger;
      FCode      := qry.FieldByName('code'        ).AsString;
      FNom       := qry.FieldByName('nom'         ).AsString;
      FSymbole   := qry.FieldByName('symbole'     ).AsString;
      FNbDecimal := qry.FieldByName('nb_decimales').AsInteger;
    end;

    Ces 2 objets héritent de TObjectFromDB dans lequel se trouve la fonction suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    procedure TObjectFromDB.LoadFor(id: Integer);
    var qry: TSQLQuery;
    begin
      qry := CreateQuery(GetQueryStr);
      qry.ParamByName('ID').AsInteger := id;
      try
        qry.Open;
     
        if (not qry.EOF) then LoadQuery(qry);
      finally
        FreeNilDataSet(qry);
      end;
    end;

    Ce code fonctionne très bien, voici par exemple un petit test :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    procedure TFrmMain.FormCreate(Sender: TObject);
    var
      d: TDevise;
      c: TChange;
    begin
      d := TDevise.Create;
      d.LoadFor(1);
      c := TChange.Create;
      c.LoadFor(1);
     
      ShowMessage(c.Nom);
      ShowMessage(d.Nom);
     
      FreeAndNil(c);
      FreeAndNil(d);
    end;

    Le problème qui apparait déjà est que pour charger un TChange, je dois faire 3 requêtes.
    1 sur la table "change" puis 2 autres pour charger chacun des TDevise qu'il utilise.

    Comment pensez-vous qu'il faille faire pour instancier proprement mes différents objets ?
    Comment organiser mon code ?

    Merci d'avance de vos réponses et conseils.

  2. #2
    Membre expert
    Avatar de e-ric
    Homme Profil pro
    Apprenti chat, bienfaiteur de tritons et autres bestioles
    Inscrit en
    Mars 2002
    Messages
    1 552
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Apprenti chat, bienfaiteur de tritons et autres bestioles

    Informations forums :
    Inscription : Mars 2002
    Messages : 1 552
    Points : 3 918
    Points
    3 918
    Par défaut
    Salut

    Je ne vois pas trop comment tu peux éviter de passer trois requêtes puisque tu accèdes à chacun de tes objets via sa clé primaire. Ce n'est pas un problème c'est une conséquence directe de ton approche. Que cherches-tu à améliorer exactement ?

    Cdlt

    M E N S . A G I T A T . M O L E M
    Debian 64bit, Lazarus + FPC -> n'oubliez pas de consulter les FAQ Delphi et Pascal ainsi que les cours et tutoriels Delphi et Pascal

    "La théorie, c'est quand on sait tout, mais que rien ne marche. La pratique, c'est quand tout marche, mais qu'on ne sait pas pourquoi. En informatique, la théorie et la pratique sont réunies: rien ne marche et on ne sait pas pourquoi!".
    Mais Emmanuel Kant disait aussi : "La théorie sans la pratique est inutile, la pratique sans la théorie est aveugle."

  3. #3
    Membre éprouvé
    Profil pro
    Développeur informatique
    Inscrit en
    Janvier 2010
    Messages
    469
    Détails du profil
    Informations personnelles :
    Âge : 66
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Janvier 2010
    Messages : 469
    Points : 1 100
    Points
    1 100
    Par défaut
    Bonjour,

    Le méthode que tu sembles choisir est de faire des objets images des tables de ta base de données, et pour chacun de ces objets des méthodes pour les lire et les mettre à jour.

    Pour cette approche, il existe déjà MorMot qui est un Framework open source en Free Pascal. Je dirais que, tant qu'à faire, il faudrait mieux l'utiliser que de réinventer la poudre.

    Ceci dit, à mon avis personnel, après quelques dizaines d'années d'expérience en Pascal, avec utilisation de plusieurs Framework "maison" mais similaires sur des très grosses applications (pas de MorMot) j'en suis arrivé à la conclusion que ce type de framework n'aide pas beaucoup. Ca devient vite lourd, nous prive souvent de la puissance de SQL et des SGBD. Il se pose aussi assez vite des problèmes de cohérence des transactions. Je ne suis en outre pas du tout sûr que cela permette de suivre plus facilement qu'une autre des évolutions de structure de la base, ce qui arrive constamment. C'est peut-être utile pour gérer des données de base, mais quand on arrive à des traitements complexes ça peut devenir contre-productif et on s'en passe avantageusement.

    Quoi qu'il en soit, pour en revenir à ton code, je ne vois pas où tu définis la requête SQL. D'une manière très générale, il me parait dangereux de mettre dans des méthodes des noms de champs qui sont définis ailleurs. Tu te reposes apparemment sur un SELECT * . Que va-t-il se passer si, dans le futur, l'ordre des champs dans la table est modifié, ou si certains sont supprimés ? Ou si tu as des bases différentes qui n'ont pas exactement les mêmes champs dans le même ordre.

    Cela fonctionne provisoirement tant que tu ne fais que des requêtes simples, mais alors il faut les multiplier, ce dont tu vois déjà l'inconvénient. Je crois que tu aurais au contraire sans doute intérêt à faire des jointures SQL, pour lire toutes tes données d'un coup, mais alors tu vas rencontrer des soucis pour affecter les champs selon ta méthode...

    Personnellement, je n'utiliserais donc pas de structures objet "globales" et "permanentes" pour stocker les données, mais seulement des structures "locales" adaptées au besoin et "proches" des requêtes qui les remplissent ou les exploitent.
    Cordialement,
    Tintinux

    Initiateur de Gestinux, une comptabilité gestion open-source, pour Linux, Windows et Mac OS.
    Une version stable et une autre en développement, avec Lazarus : vous pouvez aider à la tester, la traduire et à la développer.

  4. #4
    Invité
    Invité(e)
    Par défaut
    Merci de vos retours.

    J'avais répondu hier, mais je viens de me rendre compte que le message n'était pas passé...
    Je vais donc refaire ma réponse.


    En complément de mon premier message, j'ai bien conscience des limites de mon approche.
    C'est bien l'objet de ma demande ici, de savoir comment d'autres traite ce problème.
    Je cherche la "meilleure" solution pour ne pas avoir à répéter mon code et optimiser mon application, en évitant de faire trop de requêtes pour rien.



    Citation Envoyé par e-ric Voir le message
    Que cherches-tu à améliorer exactement ?
    Le problème de mon code est que si je veux charder tous les changes avec une centaine d'enregsitrement, je vais faire 200 requêtes sur la table devise pour créer tous les objets.

    Citation Envoyé par tintinux
    il existe déjà MorMot qui est un Framework open source en Free Pascal
    Je ne connaissais pas, je vais regarder.

    Citation Envoyé par tintinux
    Tu te reposes apparemment sur un SELECT * . Que va-t-il se passer si, dans le futur, l'ordre des champs dans la table est modifié, ou si certains sont supprimés ? Ou si tu as des bases différentes qui n'ont pas exactement les mêmes champs dans le même ordre.
    Je fais effectivement un SELECT * dans une classe parente.
    L'ordre des champs dans la base ne pose pas de problèmes.
    Il faut effectivement mettre à jour le traitement lors des modifications apportées à la base.

    Citation Envoyé par tintinux
    Je crois que tu aurais au contraire sans doute intérêt à faire des jointures SQL, pour lire toutes tes données d'un coup, mais alors tu vas rencontrer des soucis pour affecter les champs selon ta méthode...
    Le problème et que je dois alors créer tous mes objets.
    Ici, ce sont des objets simples avec peu de variables ce serait encore faisable.
    Mais quand j'aurais des objets plus complexes se ne sera plus gérable.
    Je ne trouve pas logique dans TChange de définir comment créer des TDevise.

    Citation Envoyé par tintinux
    Personnellement, je n'utiliserais donc pas de structures objet "globales" et "permanentes" pour stocker les données, mais seulement des structures "locales" adaptées au besoin et "proches" des requêtes qui les remplissent ou les exploitent.
    Je vais devoir multiplier des structures et répéter du code, cela ne me semble pas optimale.

    Merci de vos commentaires.

  5. #5
    Expert confirmé
    Avatar de anapurna
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2002
    Messages
    3 418
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mai 2002
    Messages : 3 418
    Points : 5 816
    Points
    5 816
    Par défaut
    salut,

    je ne comprend pas ta difficulté

    une requête ce compose de quelque bloc (chaîne de caractère) bien distinct
    j'en vois 5 principale
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    1°) SELECT
    2°) FROM 
    3°) WHERE 
    4°) GROUP BY 
    5°) ORDER BY
    dans le BLOC SELECT il n'y a que les nom des CHAMPS Prefixie ou non ex MonChamp ou T1.MonChamp ou MaTable.MonChamp
    le plus simple et d'utiliser un prefixe exemple D2 => D pour la table Devise 2 ... deuxième table de ta requête
    ou C1 pour la Table Change et 1 .. première table
    ce qui pourrait nous donner ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    SELECT C1.CHAMPS1,C1.CHAMPS2,D2.CHAMPS3 ....
    dans le FROM LE NOM de la TABLE Sufixé ou pas
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
       FROM Change C1 INNER-JOIN DEVISE D2 ...
    d'autre champs de jointure champs de jointures (valable depuis le SQL92 dans mon souvenir)
    le bloc WHERE ne sert que de filtre
    LE bloc group by de regroupement et le order by pour avoir un ordre

    ce qui par exemple pour construire une requête pourrait très bien ce faire comme cela
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    Function MAKE_SQL(StrSELECT,StrFROM : String;strWHERE : String = '';strGROUPBY : String = '';StrORDERBY  : String = '') : String;
    begin
      Result := '';
      if (trim(StrSELECT) <> '') and  (trim(StrFROM) <> '') Then 
      begin
         Result :=  StrSELECT+#10+#13;
         Result := Result+ StrFROM +#10+#13;
         if Trim(strWhere) <> '' Then 
           Result := Result+ strWhere+#10+#13;
         if Trim(strGROUPBY) <> '' Then 
            Result := Result+ strGROUPBY+#10+#13;
         if Trim(StrORDERBY) <> '' Then 
            Result := Result+ strGROUPBY+#10+#13;
      end; 
    end;
    tu vois qu'ici on a dissocié les différentes parties de la requête ce qui nous permet en amont de pouvoir
    gérer la construction
    j'ai simplifié le code au maximum

    pour moi la gestion de ta "Query" doit être externe a ton objet de base tu as juste besoin d'utiliser des string et de construire au dernier moment
    ta requête

    fait des recherche sur "Querybuilder"
    Nous souhaitons la vérité et nous trouvons qu'incertitude. [...]
    Nous sommes incapables de ne pas souhaiter la vérité et le bonheur, et sommes incapables ni de certitude ni de bonheur.
    Blaise Pascal
    PS : n'oubliez pas le tag

  6. #6
    Membre expérimenté

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Septembre 2003
    Messages
    733
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2003
    Messages : 733
    Points : 1 668
    Points
    1 668
    Billets dans le blog
    8
    Par défaut
    Citation Envoyé par benoit1024 Voir le message
    Le problème de mon code est que si je veux charder tous les changes avec une centaine d'enregsitrement, je vais faire 200 requêtes sur la table devise pour créer tous les objets.
    Bonjour,

    Avec cette phrase, tu as résumé toute la problématique de performance à la quelle tu seras fatalement confronté si tu ne revois pas complètement ta façon de faire.
    Le problème de modélisation et d'organisation tel que tu l'as décrit est très commun.
    Si j'ai bien compris tu as, jusqu'à présent, 2 tables. Une table Change et une table Devise.
    Et tu as 2 liens FK (Foreign Key) entre Change et Devise :
    Change.DeviseRef --> Devise.Id
    Change.Devise --> Devise.Id

    Je te suggère donc de procéder comme suit :
    1 - Commencer par charger dans une liste d'objets TListDevises ( où chaque objet de cette liste sera type TDevises) l'ensemble des devise. Tu pourra pour optimiser davantage, charger uniquement les Devises réellement utilisées :
    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    SELECT * 
    FROM  Devise 
    WHERE EXISTS (SELECT * 
                            FROM Change 
    			AND  -- ..... autre critèreS de sélection sur table Change) 
    			--on sélectionne uniquement les Devises réellement utilisées 
    			AND  (            (Change.DeviseRef = Devise.ID ) 
    				       OR  (Change.Devise = Devise.ID )  
    				)
    2 - Ensuite, il ne faudra plus créer et instancier les Objets FDeviseRef et FDevise dans la classe TChange mais au lieu de cela, il te faudra juste référencer (pointer vers) des objets déjà existants et déjà chargés dans la liste d'objets TListDevises
    Schématiquement tu auras quelques chose comme ceci :
    TChange.FDeviseRef = TDevise(TListDevises.[i])
    TChange.FDevise = TDevise(TListDevises.[j])

    Voire même (si la Devise n'existe pas !) :
    TChange.FDeviseRef = nil
    TChange.FDevise = nil


    En d'autres termes, c'est la liste d'objets TListDevises qui sera chargée de créer et d'instancier les objets TDevises une et une seule fois.

    Les objets TChange ne feront que pointer vers des objets TDevise déjà existants, déjà instanciés dans la liste TListDevises

    - En conséquence, dans la méthode TChange.LoadQuery(qry: TSQLQuery);
    Il te faudra supprimer les 2 appels ci-dessous :
    FDeviseRef.LoadFor(qry.FieldByName('devise_ref').AsInteger);
    FDevise.LoadFor(qry.FieldByName('devise').AsInteger);

    Pour les remplacer par une simple recherche de l'indice de la Devise concernée (respectivement DeviseRef et Devise dans la liste TListDevises, puis une simple affectation comme expliqué ci-haut :
    TChange.FDeviseRef := TDevise(TListDevises.[i]);
    TChange.FDevise := TDevise(TListDevises.[j]);

    Si par exemple tu as 1000 lignes Change et 20 lignes Devise, tu auras avec la méthode que je te propose, au plus 2 requêtes SQL :
    La première établie sur les Devises retournera 20 lignes
    Et la deuxième établie sur les Changes retournera 1000 lignes

    Avec ton approche actuelle, au lieu de 2 requêtes SQL tu auras :
    . Soit 1000 * (1 + 2), soit au total 3000 requêtes SQL !!! si tu charge les ligne Change une par une et les Devises une par une.
    . Soit 1 + (1000 * 2), soit au total 2001 requêtes SQL !!! si tu charges les lignes Change d'un seul bloc et les Devises une par une.

    A+
    "Une idée mal écrite est une idée fausse !"
    http://hamid-mira.blogspot.com

  7. #7
    Membre éprouvé
    Profil pro
    Développeur informatique
    Inscrit en
    Janvier 2010
    Messages
    469
    Détails du profil
    Informations personnelles :
    Âge : 66
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Janvier 2010
    Messages : 469
    Points : 1 100
    Points
    1 100
    Par défaut
    Bonjour

    Hamid a très bien expliqué comment structurer et remplir plus efficacement des objets en mémoire à partir de ta base de données.

    La question qui se pose d'abord, pour moi, est : faut-il absolument le faire, et qu'est ce que cela va t'apporter ?

    A priori, il n'est pas obligatoirement nécessaire de le faire au préalable avant tout traitement. Pour en être sûr, il faudrait évidemment en savoir plus sur l'utilisation qui sera faite de ces données, comme toujours décider de l'implémentation à partir d'une connaissance du fonctionnel.

    Par exemple, si cela doit te permettre ensuite de convertir des flux financiers d'une devise en une autre, il ne sert à rien de "charger" toutes les devises et tous les "changes", puis de parcourir tes objets en mémoire pour trouver les devises concernées et leurs valeurs, afin de faire les calculs souhaités. Une requête SQL sera, à mon avis, à la fois plus simple et plus performante. Cela n'obligera nullement à répéter du code (bien sûr, à éviter).

    D'autre part FPC ne te garantira pas, sauf développement complexe, que les données chargées restent cohérentes et à jour, alors qu'un SGBD s'occupera très bien de ce problème presque tout seul.

    Ce n'est pas parce que FPC permet de faire de la belle programmation objet qu'il faut en mettre à tout prix partout. C'est très utile dans certains cas, mais pas toujours...
    Cordialement,
    Tintinux

    Initiateur de Gestinux, une comptabilité gestion open-source, pour Linux, Windows et Mac OS.
    Une version stable et une autre en développement, avec Lazarus : vous pouvez aider à la tester, la traduire et à la développer.

  8. #8
    Invité
    Invité(e)
    Par défaut
    Merci hmira, c'est effectivement la solution qui me semble convenir le mieux à ma problématique.

    tintinux, je cherche en fait comment organiser mon code proprement pour manipuler au mieux les données.
    L'avantage des objets, qui m'intéresse ici, est d'instancier des objets qui pourront ensuite être utilisé partout dans l'application.
    De de ne pas avoir à différents endroit des manières différentes de créer des objets.

    Merci de vos réponses.

Discussions similaires

  1. code pour envoyer des données
    Par pierrrro dans le forum C++
    Réponses: 1
    Dernier message: 13/05/2011, 17h41
  2. [MySQL] bug de mon code pour afficher des donnes BDD a partir de l'id transmise
    Par gael-abdelhadi dans le forum PHP & Base de données
    Réponses: 5
    Dernier message: 20/03/2011, 12h56
  3. Charger des donnés depuis un fichier .txt
    Par Rifano dans le forum Macros et VBA Excel
    Réponses: 3
    Dernier message: 23/07/2009, 11h59
  4. Réponses: 9
    Dernier message: 29/08/2007, 09h00
  5. Réponses: 5
    Dernier message: 14/05/2006, 12h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo