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

  1. #1
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut Questions Architecture 3 tiers en programmation procédurale, Databinding et structures
    Bonjour,

    J'ai un projet de CRM déjà bien avancé. Le problème de ce projet, c'est que le code métier et UI n'ont pas été séparés. J'aimerais donc séparer les codes en utilisant une architecture type 3-tiers avec des structures et procédures. Je ne maitrise pas encore les classes et la POO pour programmer en objet donc je ne souhaite pas m'aventurer dans de la POO surtout que le projet est déjà avancé, même si apparemment c'est le mieux à faire.

    De ce fait, j'aurais besoin d'aide sur certains points qui me bloquent. J'ai bien compris le principe théorique de l'architecture 3 tiers, mais j'ai un peu de mal sur certains points pour l'appliquer dans Windev. Voici mes questions :

    Q1) Dois-je créer une collection de procédure pour CHAQUE table de l'analyse et CHAQUE requête avec la structure et le mapping correspondant ? Exemple Table Contact => créer une collection COL_Contact et une structure globale STCOL_Contact mappé sur ma table Contact ? Pareil pour ma table Société, Civilité, mes requêtes fichiers etc ?


    Q2) Pour une table possédant des clés étrangères. Comment les modéliser dans ma structure ?
    Exemple : Table contact (IDcontact, nom, prénom, IDsociete (clé étrangère de la table Société))
    Dois-je écrire pour ma Structure STCOL_Contact :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    IDsociete est un entier
    OU
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    refIDsociete est STCOL_Societe dynamique
    ? J'ai essayer de générer automatiquement mes procédures globales avec mes structures via windev "Modélisation UML > Générer code" et les clés étrangères dans mes structures ont été défini de la façon suivante : refIDsociete est STCOL_Societe dynamique pour reprendre l'exemple ci-dessus. Cependant, quand je fais un fichierversmemoire(), l'IDsociete ne se remplie donc pas automatiquement. Donc dois-je écrire IDCléEtrangères est un entier dans ma structure pour mes clés étrangères ?


    Q3) Pour initialiser une variable tableau de structure STCOL_Contact (ID, Nom, Prénom ...) , dois-je utiliser
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    tabContact est un tableau de STCOL_Contact
    ou bien
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    tabContact est un tableau de STCOL_Contact DYNAMIQUE
    ? Et que permet le "Dynamique" dans ce cas ?


    Q4) Lorsque j'initialise dans ma fenêtre ma table contact remplie par variable via databinding (sur ma variable tabContact donc), si j'ajoute ou modifie un élément de la table contact, dois-je rafraîchir simplement la table via TableAffiche() ou réexécuter la requête sur la BDD qui me permet de remplir ma variable tabContact et ensuite d'afficher de nouveau ? (Car si je rafraîchi uniquement ma table mémoire via tableaffiche(), les ajouts/modification/suppression entre temps des autres utilisateurs ne seront donc pas visibles sauf si je ferme puis reouvre la fenêtre, est-ce un fonctionnement normal ou faut-il rafraîchir toutes les données à partir de la BDD?)


    Q5) Lorsque je veux modifier une ligne de ma table contact, je souhaite ouvrir une fenêtre pour modifier ma ligne (FEN_GestionContact). Je suppose qu'il faut que je fasse quelque chose de ce type :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    pstContactCourant est un STCOL_Contact dynamique
    pstContactCourant = TABLE_Contact
     
    pstContactCourant = Ouvre(FEN_GestionContact,pstContactCourant ) 
    TableAffiche(TABLE_Contact,taCourantBandeau)
    Mais une fois dans ma FEN_GestionContact qui me permet de modifier ma ligne, dois-je faire une recherche sur la BDD à l'initialisation via le paramètre pstContactCourant.IDcontact afin de remplir les champs du contact à modifier ? (Dans le cas où ce contact aurait été modifié entre temps par un autre utilisateur). Ou j'affecte simplement mes champs par databinding via ma variable pstContactCourant passée dans la fenêtre, mais qui n'est donc peut-être plus à jour entre temps ?

    J'ai un peu de mal à saisir quand est-ce que je dois réutiliser la couche données pour mettre à jour mes variables structures.


    Q6) Dans le cas de suppression d'un contact dans ma table TABLE_Contact, est-il possible de supprimer à la fois l'élément dans la table TABLE_Contact ET dans la variable tableau tabContact (lié à la table pour le databinding) ? Car si je fais un TableSupprimeSelect(), l'élément disparaît bien de ma table TABLE_Contact, mais est toujours présent dans mon tableau tabContact. Donc si je fais de nouveau un TableAffiche(), il réapparaît dans la table TABLE_Contact.


    Si certains d'entre-vous auraient des réponses à m'apporter sur les points ci-dessus afin de m'aider à y voir plus claire et me permettre d'avancer, ça serait sympa car là je bloque...

    Merci d'avance,
    Bonne soirée,
    Esteban

  2. #2
    Membre émérite
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    juin 2017
    Messages
    1 435
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Chef de projet en SSII

    Informations forums :
    Inscription : juin 2017
    Messages : 1 435
    Points : 2 527
    Points
    2 527
    Par défaut
    Bonjour,
    R1) Si ton appli est bien structurée, tu peux très bien continuer avec des accès "Classique" à tes tables. Un contrainte que je m'impose est simplement de coder à minima dans les champs, je fais appel à des procédures/fonctions locale ou globale en fonction de la portée.
    R2) Le fait de déclarer une structure ou un tableau de structure pour "symboliser" tes FK signifie que tu vas remplir ces structures. Dans ton cas lorsque tu va lire un tuple de ta table contact, il faut lire le ou les tuples associés dans la table reliée.
    R3) Comme dans tous les langages, "Dynamique" signifie que nous ne sommes pas dans le cas d'un tableau de taille fixe. Pour utiliser ce genre de tableau, il faut faire une allocation. Une solution de contournement dans Windev est de préciser <agrandissement=taille> lors de la déclaration. Tu pourras ainsi ajouter une ligne à ton tableau de manière "transparente"
    R4)Dans tous les cas il faut ré éxécuter la requête. TableAffiche offre l'option taRéExécuteRequête
    Il y a peut être plus simple, mais ça tourne

  3. #3
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    Citation Envoyé par Voroltinquo Voir le message
    Bonjour,
    R1) Si ton appli est bien structurée, tu peux très bien continuer avec des accès "Classique" à tes tables. Un contrainte que je m'impose est simplement de coder à minima dans les champs, je fais appel à des procédures/fonctions locale ou globale en fonction de la portée.
    R2) Le fait de déclarer une structure ou un tableau de structure pour "symboliser" tes FK signifie que tu vas remplir ces structures. Dans ton cas lorsque tu va lire un tuple de ta table contact, il faut lire le ou les tuples associés dans la table reliée.
    R3) Comme dans tous les langages, "Dynamique" signifie que nous ne sommes pas dans le cas d'un tableau de taille fixe. Pour utiliser ce genre de tableau, il faut faire une allocation. Une solution de contournement dans Windev est de préciser <agrandissement=taille> lors de la déclaration. Tu pourras ainsi ajouter une ligne à ton tableau de manière "transparente"
    R4)Dans tous les cas il faut ré éxécuter la requête. TableAffiche offre l'option taRéExécuteRequête
    Bonjour, merci pour ton aide

    1) Le problème avec l'accès classique aux tables sans passer par des structures ou classes intermédiaires, c'est qu'il n'y a pas de séparation du code UI et Métier si j'ai bien compris ?
    En gros pour remplir mes champs à l'initialisation d'une fenêtre je vais devoir faire pour la méthode classique sans séparation de couche :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    Hlitrecherchepremier(Contact,IDcontact,ID) //métier
    si htrouve() //métier
    fichierversecran() //UI
    fin
    et pour remplir mes tables :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    hexecuterequete(mareq,hrequetedefaut,ID) //métier
    pour tout mareq
    tableajouteligne(table_contact,mareq.id,mareq.nom,mareq.prenom) //UI
    fin
    Donc le code UI et métier sont bien mélangés, même si j'utilise une procédure locale pour coder à minima dans ma fenêtre. Cela risque de poser des problèmes sur le long terme non ? Car c'est une grosse appli en devenir, composée de plusieurs modules (CRM, RH etc.) donc même si la CRM (premier module) est actuellement bien avancée, j'aimerais être sur d'avoir un code un minimum "propre" et maintenable sur le long terme, quitte à repartir sur de bonne base s'il est préférable dans ce cas de bien séparer l'UI et le code métier, avant d'attaquer les autres modules et qu'il soit trop tard ensuite pour revenir en arrière...


    2) Dans ce cas, si je souhaite uniquement récupérer l'ID, je n'ai pas besoin de déclarer ma FK comme une structure ? Un simple "IDsociete est un entier" suffit ?


    4) Le problème, c'est que ma table est bindée sur ma variable tableau de structure STCOL_Contact, donc je ne peux pas utiliser le taRéExécuteRequête de tableaffiche(). Et un simple tableaffiche() va simplement mettre à jour la table à partir de ma variable tableau de structure STCOL_Contact qui n'est pas forcément à jour avec les données qui auraient pu être ajoutées/modifiées/supprimées coté serveur.
    Donc si j'ai bien compris, à chaque modification, ajout de contact, je suis obligé d'actualiser toute la table et donc de refaire le traitement coté serveur que je fais à l'initialisation de ma fenêtre pour charger ma table ? C'est à dire rappeler ma procédure globale qui exécute la requête pour récupérer tous mes contacts, qui les affectent à ma variable tableau de structure et qui me renvoie cette variable tableau pour que je puisse faire ensuite un tableaffiche() pour mettre à jour ma table via databinding avec ma variable tableau récupérée ?


    En tout cas merci d'avoir pris le temps de me répondre

  4. #4
    Membre émérite
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    juin 2017
    Messages
    1 435
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Chef de projet en SSII

    Informations forums :
    Inscription : juin 2017
    Messages : 1 435
    Points : 2 527
    Points
    2 527
    Par défaut
    Tu Peux éventuellement voir du côté du RAD MVP ne serait-ce que pour comprendre le squelette
    Il y a peut être plus simple, mais ça tourne

  5. #5
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    Q1-En objet chaque table à sa classe quand on procède avec le pattern ORM, qui contient dans ses attributs toutes les rubriques de la table. C'est ce que tu proposes de faire en remplacant la classe par une collection de procédure et ses attributs par une structure. L'approche est bonne mais n'oublie pas qu'à chaque mise à jour de l'analyse, tes structures et toutes les procédures crées seront possiblement à contrôler si Windev n'en permet pas la génération (je n'ai pas utilisé de modèle UML jusque là donc je ne connais pas les capacités de génération de Windev dans ce cas). Est-ce que tu dois? Non. Comme le dit Voroltinquo, ça n'est pas obligatoire si ton projet est structuré et je ne peux qu'appuyer son argument: rien derrière les éléments de l'IHM, c'est un vrai carnage ensuite pour la factorisation du code et sonn nettoyage. Tout doit être encapsulé dans des procédures locales/globales
    Je dirais quand même que l'usage de la POO avec du databinding Fichier-Classe (fonctions FichierVersMémoire/MémoireVersFichier ou FichierVersTableau) ira bien pour des volumes de données faibles, mais si les volumes augmentent, l'objet se révèlera bien lent; il en va de même pour les structures. Tout charger en mémoire n'est pas systématiquement une bonne idée surtout si l'application fonctionne avec une base distante.
    Le FichierVersEcran, ce n'est pas incompatible avec un code propre si c'est bien utilisé dans les procédures adéquates (locales, en conjugaison avec le traitement "Demande de mise à jour de l'affichage" de la fenêtre). Néanmoins, je préfère bannir cette instruction et passer par des variables en mémoire qui sont chargées avec la base, car le FichierVersEcran lie la couche données à la couche présentation, ce qui n'est pas vraiment du 3 tiers. Il manque un tiers : le traitement, censé encapsuler les codes métiers.

    Q2-Il est préférable que les clés étrangères (les FK) soient identifiées. Quand je conçois les bases elles sont préfixées IDX et se retrouvent nommée m_IDX dans les objets, mais en procédural il est possible en effet de leur donner un nom qui est formatté et qui indique bien que c'est une clé étrangère donc les préfixer de ref est une bonnée idée. Quand à ce que dit Voroltinquo, le fait de charger un objet X qui contient la clé étrangère de Y n'implique pas forcément de charger Y. En fait, cela dépend des cas. Je sais que parfois dans mon code je n'aurais pas besoin de charger l'ensemble des objets liés donc je ne le faisais que si c'est nécessaire en instanciant un objet à part. C'était une ancienne méthode que j'utilisais. Plus tard, j'ai utilisé des JOIN dans les requêtes ce qui rendait les temps de chargement tellement court que chaque objet unitaire était systématiquement chargé dans son parent, même si c'était désactivable. Pour les éléments multiples (la liste des factures d'un client) ce sont des tableaux dont le chargement n'était fait qu'à la demande car c'est long à requêter.
    Donc écrire directement refIDsociete est STCOL_Societe dynamique, je tiques un peu. refIDSociete n'est pas un STCOL_Societe mais un entier dans la base. STCOL_Societe c'est la représentation par code de cette table, ça ne désigne pas tout à fait la même chose. Quand tu coderas, tu devras prévoir de quoi charger ces objets, c'est une surcharge qui ne servira peut être pas toujours comme je l'expliques plus haut. Je ferais donc comme tu le dis dans ton deuxième message: refIDSociete est un entier.


    Q3-Dynamique en objet c'est déclarer un objet sans l'allouer. Ne pas préciser dynamique c'est en fait instancier l'objet sans qu'on l'ai explicitement demandé et donc il occupera de la place en mémoire dès la déclaration. Alors que si on le déclare dynamique il faudra faire une autre ligne de code pour l'allouer.
    Néanmoins, ta question soulève un point auquel je ne sais pas répondre et je l'ai donc testé sous WD25:

    Ce code
    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
     
    stStruct est une Structure
    	nom est chaîne
    	prenom est une chaîne
    	agee est entier
    FIN
     
    oSt est une stStruct dynamique
    oST2 est une stStruct
     
    tabST est un tableau de stStruct dynamique
    tabST2 est un tableau dynamique de stStruct
     
    TableauAjoute(tabST,oSt)
    TableauAjoute(tabST2,oST2)

    Compile et s'execute. Au débuggeur, tabST contient un élément mais comme oST est dynamique cet élément vaut NULL, alors que oST2 dans son tableau tabST2 ne vaut pas Null: il est bien vivant en mémoire et possède les valeurs par défaut de ses types de données (2 chaines vide et 0 dans l'entier)

    Si tu remplaces
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    tabST2 est un tableau dynamique de stStruct
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    tabST2 est un tableau de stStruct
    le code fonctionne encore. D'après la doc (https://doc.pcsoft.fr/fr-FR/?1514057) , ce sont 2 écritures identiques: "Il est conseillé d'utiliser : Un tableau dynamique ou un tableau "simple" lorsque la taille du tableau doit être modifiée au cours du programme". Donc je n'utilises que des tableaux simples. Et à ta place je mettrais donc tabContact est un tableau de STCOL_Contact: le tableau sera vide, pourra être agrandi via TableauAjoute, mais uniquement par des éléments qui de toute façon vont exister (des STCol_Contact) et qui pourront être dynamiques ou pas: tu devras forcément les instancier pour qu'ils contiennent quelque chose. Ajouter une structure dynamique à un tableau ne fonctionne pas: si tu fais

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TableauAjoute(tabST2 ,oSt)
    Windev crash en te disant "Vous avez appelé la fonction TableauAjoute. L'objet à affecter n'est pas alloué."

    Q4-Penses bien à segmenter où sont les données dans ta tête: si un autre modifie la base, tu ne verras rien chez toi tant que tu n'auras pas réexécuté la requête car les données que tu affiches viennent du databinding d'un tableau en mémoire, donc viennent de la mémoire. La mémoire n'est pas forcément à jour de la BDD. Si quand tu ouvres la fenêtre tu fais un chargement de tabContact depuis la BDD mais qu'ensuite tu ne requêtes plus la BDD, tu ne verras jamais les modifs des autres. Donc quand tu veux rafraichir, requêtes la base comme dit Voroltinquo (et attention aux SELECT *....si tu commences à avoir beaucoup de données, ça va devenir lent.), afin de mettre le contenu de la base (le tier données) dans la structure en mémoire (le tiers traitement) pour les afficher ensuite à l'écran (le tiers présentation). Comme tu le dis, à chaque requête, tu recharges tout. Tu vois donc les limites de ce modèle que j'explique plus haut: si t'as une table de 5000 lignes, tu vas tout prendre dans la tête en mémoire, avec le temps de traitement qui va avec.

    Q5-Y'a 2 approches: soit ta structure contient déjà tout et tu demande à la fenêtre d'afficher la structure en mémoire, soit tu passes l'ID à la fenêtre et tu demandes à la fenêtre de charger elle même l'élément à modifier. Je préfère cette deuxième approche ne serait-ce que pour le cas d'une appli fortement concurentielle (c'est à dire une appli utilisée en même temps par beaucoup d'utilisateurs). Charger une seule structure via un ID est très rapide, autant donc le faire au dernier moment pour avoir la donnée la plus à jour possible.

    Q6-TableSupprimeSelect() ne touche pas à la variable databindée. Fais l'inverse: quand le tier présentation demande au tiers traitement de supprimer une donnée, supprimes la dans ta variable mémoire (c'est le tier traitement qui fait la suppression dans sa propre couche), répercutes ça dans la base et réaffiche ton IHM: soit en direct depuis la mémoire sans récupérer depuis la base, soit en éxécutant la requête à nouveau pour remplir la mémoire, et l'IHM à partir de la mémoire.

  6. #6
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    Merci à vous deux pour vos réponses et explications

    Citation Envoyé par kunnskap Voir le message
    Q1-En objet chaque table à sa classe quand on procède avec le pattern ORM, qui contient dans ses attributs toutes les rubriques de la table. C'est ce que tu proposes de faire en remplacant la classe par une collection de procédure et ses attributs par une structure. L'approche est bonne mais n'oublie pas qu'à chaque mise à jour de l'analyse, tes structures et toutes les procédures crées seront possiblement à contrôler si Windev n'en permet pas la génération (je n'ai pas utilisé de modèle UML jusque là donc je ne connais pas les capacités de génération de Windev dans ce cas). Est-ce que tu dois? Non. Comme le dit Voroltinquo, ça n'est pas obligatoire si ton projet est structuré et je ne peux qu'appuyer son argument: rien derrière les éléments de l'IHM, c'est un vrai carnage ensuite pour la factorisation du code et sonn nettoyage. Tout doit être encapsulé dans des procédures locales/globales
    Je dirais quand même que l'usage de la POO avec du databinding Fichier-Classe (fonctions FichierVersMémoire/MémoireVersFichier ou FichierVersTableau) ira bien pour des volumes de données faibles, mais si les volumes augmentent, l'objet se révèlera bien lent; il en va de même pour les structures. Tout charger en mémoire n'est pas systématiquement une bonne idée surtout si l'application fonctionne avec une base distante.
    Le FichierVersEcran, ce n'est pas incompatible avec un code propre si c'est bien utilisé dans les procédures adéquates (locales, en conjugaison avec le traitement "Demande de mise à jour de l'affichage" de la fenêtre). Néanmoins, je préfère bannir cette instruction et passer par des variables en mémoire qui sont chargées avec la base, car le FichierVersEcran lie la couche données à la couche présentation, ce qui n'est pas vraiment du 3 tiers. Il manque un tiers : le traitement, censé encapsuler les codes métiers.
    Juste pour être dans le contexte, le projet que je développe est un projet sur plusieurs modules, le premier étant une CRM avec gestion de contact, sociétés, opportunités, offres, etc avec possibilité de recherche. Sur une fiche contact, on a plusieurs onglets, avec par exemple les familles de produits pour ce contact, les moyens de communication etc. Ce sont des tables mémoires avec bouton ajouter, modifier, supprimer pour chacune des tables. (Le bouton ajout/modif ouvre la fiche de la table correspondante). Il n’y aura normalement jamais 5000 enregistrements d’un coup à afficher pour une même table. (Sauf lors de la recherche d’un contact si on décide d’ouvrir la table complète des contacts, sans utiliser de filtre par exemple)

    En effet, ça serait un pattern ORM. D’après ce que j’ai vu dans Windev, il est possible de générer la classe (et la régénérer par la suite si modification dans l’analyse pour mettre à jour le mapping) en cliquant dessus > « Générer le modèle de classe ». On a alors une classe mappé sur le fichier et chaque attribut est mappé sur la rubrique de la table. C’est un peu le principe du RAD MVP que ma conseillé Voroltinquo, il génère la classe modèle de la sorte pour les fichiers de l’analyse et en prime une classe TableauDeLaClasseGénérée qui possède un attribut « *** est un tableau de LaClasseGénérée dynamique » avec une méthode Charger (pour charger les éléments dans le tableau mémoire via requête), une méthode Ajouter (Pour ajouter un élément au tableau mémoire par la suite) et une méthode Supprimer. Il y a également une classe de base MBase dont toutes les classes héritent et qui possèdes les méthodes d’ajout, de modification, et de suppression d’éléments dans la BDD. (MemoireVersFichier() > Hajoute / Hmodifie)
    Le principe a l’air bien, mais je ne souhaite pas utiliser un pattern MVP, du moins je préfère continuer de programmer la couche Présentation avec des procédures locales/globales plutôt qu’utiliser des classes présentations que je trouve assez complexe.

    Q1) En gros pour de l’ORM, je devrais générer la classe modèle de la table avec le mapping, ET une classe TableauDeCetteClasse pour récupérer les éléments de ma table ? Et ça pour chacune des tables de mon analyse ?
    Q2) Et concernant mes requêtes qui récupèrent des éléments sur plusieurs tables ? Je devrais également créer une classe avec les attributs de cette requête pour pouvoir les databinder sur ma table visuelle ?
    Q3) J’ai également une recherche avancée, où j’ai plein de critères de recherche, je ne peux donc pas prévoir d’avance mes attributs, ni le nombre d’éléments à charger. Je suppose que pour ma fonctionnalité recherche avancée, je ne pourrais pas faire du 3 tiers au risque de perdre en performance ?


    Le truc c’est comme tu dis, le fichierversecran lie la couche donnée et présentation, je souhaitais justement avoir un tiers intermédiaire traitement, d’où ma demande sur ce pattern ORM. Étant donné que tu dis bannir également cette instruction, tu utilises donc également l’architecture 3 tiers avec le databinding sur les classes et ce pattern ORM ?


    Citation Envoyé par kunnskap Voir le message
    Q2-Il est préférable que les clés étrangères (les FK) soient identifiées. Quand je conçois les bases elles sont préfixées IDX et se retrouvent nommée m_IDX dans les objets, mais en procédural il est possible en effet de leur donner un nom qui est formatté et qui indique bien que c'est une clé étrangère donc les préfixer de ref est une bonnée idée. Quand à ce que dit Voroltinquo, le fait de charger un objet X qui contient la clé étrangère de Y n'implique pas forcément de charger Y. En fait, cela dépend des cas. Je sais que parfois dans mon code je n'aurais pas besoin de charger l'ensemble des objets liés donc je ne le faisais que si c'est nécessaire en instanciant un objet à part. C'était une ancienne méthode que j'utilisais. Plus tard, j'ai utilisé des JOIN dans les requêtes ce qui rendait les temps de chargement tellement court que chaque objet unitaire était systématiquement chargé dans son parent, même si c'était désactivable. Pour les éléments multiples (la liste des factures d'un client) ce sont des tableaux dont le chargement n'était fait qu'à la demande car c'est long à requêter.
    Donc écrire directement refIDsociete est STCOL_Societe dynamique, je tiques un peu. refIDSociete n'est pas un STCOL_Societe mais un entier dans la base. STCOL_Societe c'est la représentation par code de cette table, ça ne désigne pas tout à fait la même chose. Quand tu coderas, tu devras prévoir de quoi charger ces objets, c'est une surcharge qui ne servira peut être pas toujours comme je l'expliques plus haut. Je ferais donc comme tu le dis dans ton deuxième message: refIDSociete est un entier.
    Le mieux étant donc d’écrire refIDSociete est un entier et de générer la classe société à part au besoin ? Je prends note, mes clés étrangères sont identifiés par la génération de la classe quoi qu’il arrive étant donné que j’utilise des préfixes dans mes tables de l’analyse qui identifie la table en question.


    Citation Envoyé par kunnskap Voir le message
    Q3-Dynamique en objet c'est déclarer un objet sans l'allouer. Ne pas préciser dynamique c'est en fait instancier l'objet sans qu'on l'ai explicitement demandé et donc il occupera de la place en mémoire dès la déclaration. Alors que si on le déclare dynamique il faudra faire une autre ligne de code pour l'allouer.
    Néanmoins, ta question soulève un point auquel je ne sais pas répondre et je l'ai donc testé sous WD25:

    Ce code
    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
     
    stStruct est une Structure
    	nom est chaîne
    	prenom est une chaîne
    	agee est entier
    FIN
     
    oSt est une stStruct dynamique
    oST2 est une stStruct
     
    tabST est un tableau de stStruct dynamique
    tabST2 est un tableau dynamique de stStruct
     
    TableauAjoute(tabST,oSt)
    TableauAjoute(tabST2,oST2)

    Compile et s'execute. Au débuggeur, tabST contient un élément mais comme oST est dynamique cet élément vaut NULL, alors que oST2 dans son tableau tabST2 ne vaut pas Null: il est bien vivant en mémoire et possède les valeurs par défaut de ses types de données (2 chaines vide et 0 dans l'entier)

    Si tu remplaces
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    tabST2 est un tableau dynamique de stStruct
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    tabST2 est un tableau de stStruct
    le code fonctionne encore. D'après la doc (https://doc.pcsoft.fr/fr-FR/?1514057) , ce sont 2 écritures identiques: "Il est conseillé d'utiliser : Un tableau dynamique ou un tableau "simple" lorsque la taille du tableau doit être modifiée au cours du programme". Donc je n'utilises que des tableaux simples. Et à ta place je mettrais donc tabContact est un tableau de STCOL_Contact: le tableau sera vide, pourra être agrandi via TableauAjoute, mais uniquement par des éléments qui de toute façon vont exister (des STCol_Contact) et qui pourront être dynamiques ou pas: tu devras forcément les instancier pour qu'ils contiennent quelque chose. Ajouter une structure dynamique à un tableau ne fonctionne pas: si tu fais

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    TableauAjoute(tabST2 ,oSt)
    Windev crash en te disant "Vous avez appelé la fonction TableauAjoute. L'objet à affecter n'est pas alloué."
    Merci pour les explications, je testerais également de mon côté pour que ça soit encore plus claire


    Citation Envoyé par kunnskap Voir le message
    Q4-Penses bien à segmenter où sont les données dans ta tête: si un autre modifie la base, tu ne verras rien chez toi tant que tu n'auras pas réexécuté la requête car les données que tu affiches viennent du databinding d'un tableau en mémoire, donc viennent de la mémoire. La mémoire n'est pas forcément à jour de la BDD. Si quand tu ouvres la fenêtre tu fais un chargement de tabContact depuis la BDD mais qu'ensuite tu ne requêtes plus la BDD, tu ne verras jamais les modifs des autres. Donc quand tu veux rafraichir, requêtes la base comme dit Voroltinquo (et attention aux SELECT *....si tu commences à avoir beaucoup de données, ça va devenir lent.), afin de mettre le contenu de la base (le tier données) dans la structure en mémoire (le tiers traitement) pour les afficher ensuite à l'écran (le tiers présentation). Comme tu le dis, à chaque requête, tu recharges tout. Tu vois donc les limites de ce modèle que j'explique plus haut: si t'as une table de 5000 lignes, tu vas tout prendre dans la tête en mémoire, avec le temps de traitement qui va avec.
    Oui j’ai bien compris que pour récupérer les données qui auraient été ajoutées par les autres utilisateurs, il faudrait que je requête la base pour mettre le contenu dans la classe puis ensuite afficher à l’écran par databinding.
    Mais la question, pour reprendre le contexte de mon projet où j’ai par exemple la table communication sur une fiche contact. Si j’ajoute un moyen de communication pour ce contact, dois-je nécessairement requeter la base ensuite afin de rafraîchir les possibles modifs extérieures ou un simple ajout de ma ligne dans ma classe mémoire + tableaffiche() serait plus logique ? Vu que je peux actualiser la fiche du contact avec un bouton Actualiser qui cette fois-ci rafraîchirait tout.
    En gros lors d’un ajout ou d’une modification d’une ligne d’une table (Communication, famille produits etc) de ma fiche contact, est-ce plus logique d’ajouter simplement la ligne que je viens de créer (comme un tableajouteligne()) plutôt que rafraichir toute la table via requête sur le serveur ? Ou c’est mieux d’utiliser un bouton actualiser qui actualiserait toutes les données extérieures, comme lors du chargement de la fiche du contact à l’ouverture.
    Car le but d'utiliser des variables mémoires, c'est pas aussi justement d'éviter de faire trop de requêtes sur le serveur et de ce fait travailler le plus possible sur les tableaux mémoires générés sans avoir à les rafraîchir à chaque ajout/modifs ?


    Citation Envoyé par kunnskap Voir le message
    Q5-Y'a 2 approches: soit ta structure contient déjà tout et tu demande à la fenêtre d'afficher la structure en mémoire, soit tu passes l'ID à la fenêtre et tu demandes à la fenêtre de charger elle même l'élément à modifier. Je préfère cette deuxième approche ne serait-ce que pour le cas d'une appli fortement concurentielle (c'est à dire une appli utilisée en même temps par beaucoup d'utilisateurs). Charger une seule structure via un ID est très rapide, autant donc le faire au dernier moment pour avoir la donnée la plus à jour possible.
    Actuellement c’est ce que je fais, je passe l’ID dans ma fenêtre pour afficher les données de la ligne à modifier. A la validation, je rafraichis toute la table via tableaffiche(table,tareexecuterequete) (vu que je n’utilise pas encore d’architecture 3 tiers).
    Mais j’ai vu dans l’exemple Initiation au MVP de windev qu’eux passaient l’objet dans la fenêtre pour les modifications et lors d’un ajout, ajoutaient simplement l’élément au tableau mémoire + tableaffiche(), donc pas de rafraichissement totale de la table via requête sur le serveur. C’est pour cela que je me demande quel est vraiment le meilleur principe à utiliser et il y a-t-il un réel intérêt à rafraichir tout via requête sur bdd à chaque ajout / modif d’élément.


    Citation Envoyé par kunnskap Voir le message
    Q6-TableSupprimeSelect() ne touche pas à la variable databindée. Fais l'inverse: quand le tier présentation demande au tiers traitement de supprimer une donnée, supprimes la dans ta variable mémoire (c'est le tier traitement qui fait la suppression dans sa propre couche), répercutes ça dans la base et réaffiche ton IHM: soit en direct depuis la mémoire sans récupérer depuis la base, soit en éxécutant la requête à nouveau pour remplir la mémoire, et l'IHM à partir de la mémoire.
    En effet, c'est plus logique dans ce sens !


    Cordialement,
    Esteban

  7. #7
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    Pour ton intro:
    -effectivement dans une majorité de cas on a pas des milliers de lignes à afficher et même quand c’est le cas, je crée un objet light à part et je ne fais pas un SELECT * bien sur. Mais c’était juste pour bien avertir d’une limitation à laquelle on ne pense pas au début quand la base de données est toute petite
    -les assistants de génération auto de WD style RAD, c’est noway pour moi. Je n’ai certes pas retesté depuis longtemps mais le code généré n’était pas propre pour moi et la manière de nommer les procédures (rien que ça) ne colle pas à mon lower camel case…je me contente de générer la classe et ça me suffit, je crée le reste des méthodes moi même si elles sont nécessaires. Par contre la classe mère dont tu parles je ne la connais pas, et je vais réinvestiguer un peu du côté du MVP

    Q1) Non, quand on veut avoir une liste de client j’instancie un objet client et j’ai dedans une méthode permettant le renvoi d’un tableau de client dont je fais ce que je veux dans le code appelant (où ce tableau doit être déclaré). Par contre chaque table de l’analyse possède en effet sa classe. Et attention à la modélisation des tables de jointures.

    Q2) Je ne crée pas de classes basées sur des requêtes, sauf si la requêtes est une vue (que j’assimilerai donc à une table et pour laquelle je vais créer une classe modèle)

    Q3) Là je sais pas trop. C’est un rêve de décideur ça , avoir des critères de recherche dans tous les sens. Si tu te mets à chercher sur une rubrique qui n’est pas indexée ça va forcément être plus lent. Soit tu design ta base d’une autre façon, qui ne t’est peut être pas encore accessible si tu as une XP inférieure à 1 ou 2 ans, (et cette conception relève d’un DBA, je ne peux pas y répondre comme pour le développement), soit tu design le système de recherche de manière à favoriser d’abord la sélection de critères sur les colonnes indexées; de sorte que chaque recherche va d’abord très rapidement réduire le nombre de possibilités, et qu’au final la portion de données qu’on va devoir parcourir en intégralité car la colonne sur laquelle on cherche n’est pas indexée, soit la plus réduite possible.

    Pour le reste:

    Oui, j’utilises quasi exclusivement cette architecture mais je fais essentiellement de la POO. Donc analyse, génération de la classe et mapping automatique des attributs, création des méthodes d’accès à la base (directement dans la classe parfois, par héritage le plus souvent quand même…), encapsulation de tous les traitements métiers dans la classe. En fait, ce sont les classes qui possèdent l’intelligence, si tu veux changer la base, tu changes certaines méthodes des classes mais les IHM ne doivent pas bouger. L’IHM est bête, et ne fais que retransmettre les ordres de l’utilisateur à la classe qui va traiter et déclencher des réaffichage si besoin (callback), ou laisser l’IHM se débrouiller avec le retour de la classe…bref c’est un vaste sujet.

    Pour refIDSociete, c’est un entier dans la base, donc pour moi ça doit être un entier dans la modélisation de la table qui le contient. Et à côté tu as en effet une table reliée qui aura sa propre classe et que tu ne chargeras que si besoin.

    Pour la Q4: Le but des variables mémoires, et de l’archi 3 tiers qu’elles induisent, c’est d’abord de placer une couche d’abstraction entre la base de données et l’IHM, et surtout une couche qui va encaisser les traitements métier, que d’ordinaire je vois codés derrière l’IHM directement. Après oui, tu peux si tu veux ne pas requêter systématiquement la base et te contenter d’ajouter les données en mémoire: il n’y a pas de bon ou mauvais choix dans ce cas. Mon choix à moi, c’est de requêter le serveur, car mes requêtes de liste sont légères et que je préfère que tout soit à jour, surtout si l’utilisateur a passé 4 minutes à remplir sa fiche, puis 20 minutes à la machine a café, et qu’il est revenu valider son formulaire après.

    Pour la Q5: ce que PC SOFT ne t’as pas dit c’est que quand on teste unitairement une fenêtre qui a besoin de paramètre, tu peux demander à Windev de passer un ID à la fenêtre, et la fenêtre se lance puis récupère ce qu’il faut. Si il faut passer un objet à la fenêtre, tu fais comment? Plus de test unitaire possible, c’est un peu plus chiant. Mais bon, j’ai déjà fait les 2 dans mes projets, je ne dirais pas que c’est un drame, mais saches le. Pour le rafraichissement après, voir ma réponse ci dessus, leur choix est de ne pas rafraichir, ça n’est pas le mien pour les raisons que j’ai données.

  8. #8
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    Merci pour ta réponse,

    Citation Envoyé par kunnskap Voir le message
    Q1) Non, quand on veut avoir une liste de client j’instancie un objet client et j’ai dedans une méthode permettant le renvoi d’un tableau de client dont je fais ce que je veux dans le code appelant (où ce tableau doit être déclaré). Par contre chaque table de l’analyse possède en effet sa classe.
    Aurais-tu un exemple à me montrer d'une classe possédant une méthode de renvoi de tableau de sa propre classe ? J'ai bien compris le principe mais j'ai un peu de mal à saisir la façon de faire en objet, du moins en une seule classe, alors si tu as un exemple d'instanciation d'objet avec méthode de renvoi de tableau, ça serait top. Car dans leur exemple sur windev, ils ont une classe modèle mappé sur la table, et une classe Tableau de cette classe qui possède la méthode pour remplir ce tableau. De ce fait, dans le code ils instancient directement la classe Tableau puis charge le tableau avec la méthode.
    En procédurale avec structure, je créais une variable "gtabListeContact est un tableau de STCOL_Contact" dans mon code, puis gtabListeContact=COL_Contact.gP_Lst_Contact(gnContactID). Et dans ma procédure globale gP_Lst_Contact(gnContactID) j'ai un RENVOYER tabContact qui renvoyait mon tableau rempli.

    Autre question : J'ai pas moins d'une centaine de tables pour l'instant, donc possiblement 100 classes à mapper, est-ce que cela risque d'avoir également un impact négatif coté performance d'avoir autant de classes ? J'aurais par la suite d'autres tables qui vont s'ajouter.



    Citation Envoyé par kunnskap Voir le message
    Et attention à la modélisation des tables de jointures.
    Aurais-tu également un exemple afin de générer convenablement une classe sur une table de jointure ? J'ai testé de générer automatiquement la classe d'une table de jointure. J'ai ma table Contact et une table Role, un contact peut avoir plusieurs rôles, donc j'ai une table jointure Contact_Role. En générant la classe basé sur ma table j'ai donc :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    MContact_Role est une Classe <MAPPING=Contact_Role>
    	<MAPPING>
    	m_sCONTid		est un entier 	<MAPPING=CONTid>
    	m_nROLEid		est un entier 	<MAPPING=ROLEid>
    	m_bROLEparDefaut	est un booléen	<MAPPING=ROLEparDefaut>
    	<FIN>
    FIN
    Autre question hors sujet : Windev me génère automatiquement mes tables avec jointures quand c'est nécessaire pour lier mes tables, ces tables ne possèdent pas d'ID auto propre à ces tables (mais une clé composée regroupant les deux tables jointes), faut-il que je rajoute un ID automatique dans chacune des tables de jointure ? (Comme si je créais manuellement ma table de jointure, donc possédant un ID automatique, et que j'associais ensuite les ID des tables à joindre).



    Citation Envoyé par kunnskap Voir le message
    Q2) Je ne crée pas de classes basées sur des requêtes, sauf si la requêtes est une vue (que j’assimilerai donc à une table et pour laquelle je vais créer une classe modèle)
    C'est là que j'ai du mal à comprendre, comment récupères-tu les éléments obtenus par une requête basée sur plusieurs tables si chacune de tes classes est mappée par rapport à une table de l'analyse ? Par exemple pour revenir à ma table Contact_Role. Si je souhaite afficher tous les rôles de mon Contact, j'ai donc une requête sur ma table Contact_Role (CONTid, ROLEid), paramètre sur CONTid, jointure avec ma table Role (ROLEnom) afin de récupérer non pas mes ID rôles mais les noms des rôles. Cependant, dans ma classe générée pour Contact_Role, j'ai uniquement les attributs CONTid et ROLEid, pas ROLEnom. Dans ce cas, comment je peux mapper et renvoyer dans un tableau mémoire les éléments de ma requête si je n'ai pas de classe spécifique et mappée sur les attributs de ma requête ?



    Citation Envoyé par kunnskap Voir le message
    Q3) Là je sais pas trop. C’est un rêve de décideur ça , avoir des critères de recherche dans tous les sens. Si tu te mets à chercher sur une rubrique qui n’est pas indexée ça va forcément être plus lent. Soit tu design ta base d’une autre façon, qui ne t’est peut être pas encore accessible si tu as une XP inférieure à 1 ou 2 ans, (et cette conception relève d’un DBA, je ne peux pas y répondre comme pour le développement), soit tu design le système de recherche de manière à favoriser d’abord la sélection de critères sur les colonnes indexées; de sorte que chaque recherche va d’abord très rapidement réduire le nombre de possibilités, et qu’au final la portion de données qu’on va devoir parcourir en intégralité car la colonne sur laquelle on cherche n’est pas indexée, soit la plus réduite possible.
    Oui, j'ai que quelques mois d'expériences qui plus est uniquement sur mes propres recherches, c'est pour cela aussi que j'essaye de trouver une manière de programmer "simple" qui m'est accessible pour mon expérience, mais aussi efficace et structurée pour ne pas avoir un projet qui ne soit plus gérable au fil de son développement. De plus, le projet sur windev sera "webisé" par la suite, donc autant prévoir d'avance une séparation des codes UI/Métiers afin de ne pas avoir à tout recoder par la suite au passage sur webdev. Mais les exemples sont assez limités sur le web pour le 3 tiers sur Windev, voir inexistant pour des analyses avec des tables jointes, pas faute d'avoir cherché.

    La seconde façon de faire me parait correcte, j'en prends note merci



    Citation Envoyé par kunnskap Voir le message
    Pour la Q4: Le but des variables mémoires, et de l’archi 3 tiers qu’elles induisent, c’est d’abord de placer une couche d’abstraction entre la base de données et l’IHM, et surtout une couche qui va encaisser les traitements métier, que d’ordinaire je vois codés derrière l’IHM directement. Après oui, tu peux si tu veux ne pas requêter systématiquement la base et te contenter d’ajouter les données en mémoire: il n’y a pas de bon ou mauvais choix dans ce cas. Mon choix à moi, c’est de requêter le serveur, car mes requêtes de liste sont légères et que je préfère que tout soit à jour, surtout si l’utilisateur a passé 4 minutes à remplir sa fiche, puis 20 minutes à la machine a café, et qu’il est revenu valider son formulaire après.
    Actuellement c'est également ce que je fais pour du 2 tiers pour les mêmes raisons que celles que tu as donnée. Après en 3 tiers, j'ai juste peur que le traitement soit plus long si j'actualise à chaque modification étant donné qu'il y a un traitement en plus qu'en 2 tiers pour afficher ma table. J'ai des requêtes basées sur plusieurs tables de l’analyse pour remplir mes tables, même si celles-ci ne retournent pas énormément de résultats


    Cordialement,
    Esteban

  9. #9
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    Q1-Pour l’exemple je peux t’en proposer un mais je suis sous WD25, je dois savoir quelle version tu as sinon tu ne pourras pas ouvrir le projet. Je peux faire un zip en téléchargement car le code ne va pas rentrer dans un message de forum ça risque d’être compliqué à caser

    Pour ton autre question: sur un plan purement temporel, oui, l’objet a un impact. Ajouter de l’abstraction augmente le temps de traitement. Mais sur un plan pratique, on parle de millisecondes, ce sont des traitements processeur/mémoire. Donc le surcoût est finalement assez faible en comparaison de la maintenabilité plus facile à long terme et des possibilités que ça apporte. L’objet se paye en temps pendant les phases d’étude (la conception est plus lente) et en temps d’exécution sur le poste (qui est plus lente aussi) mais par la suite les avantages apparaissent.

    Pour les tables de jointure, je peux aussi te joindre un exemple mais ce n’est pas très grave c’est plutôt pour ce que tu dis en Q2 justement. Pour ta question sur l’ID Auto, un prestataire avec lequel j’ai bossé m’a donné la réponse.
    Dans tes rôles, un contact peut avoir plusieurs rôles et un rôle peut donc appartenir à plusieurs contact (de mémoire c’est ainsi que Windev le formule dans l’assistant). Quand tu veux modifier les rôles d’un contact tu as 2 solutions: supprimer tous les rôles existants dans la table de jointure et les recréer selon ton bon vouloir; ou supprimer les rôles inutiles et n’ajouter/modifier que les rôles nécessaires. Dans le premier cas, tes lignes sont supprimées, l’ID Auto avec donc. Dans le deuxième cas, l’ID Auto perdure puisque des lignes vont rester. Si tu choisis la première solution, inutile de mettre d’ID Auto puisqu’il ne servira qu’assez peu, les lignes se feront exploser régulièrement. Dans le deuxième cas, l’ID Auto a davantage d’intérêt puisqu’il reste dans la table et que le moteur pourra s’en servir pour optimiser ses requêtes si il veut.

    Q2-Justement ce que je dis c’est que dans ce cas, je peux créer une classe qui est le reflet du résultat de la requête. Une requête de ce type est un SELECT qui va renvoyer un certain nombre de champs: ces champs seront les attributs de ma classe, mappés sur les champs de la requête. Au départ il y a longtemps, je récupérais ces infos via des property dans la classe, et les property requêtaient la base mais c’était plus long.

    Q3-Oui alors la webisation, attention hein. Je travaille sur des projets de plusieurs années, quasi entièrement en procédural, si tu arrives à Webiser des app comme ça en un clic avec le bouton tu m’appelles c’est une pub de PC SOFT ça. Si le code et l’IHM ne sont pas rigoureusement conçus, c’est inwebisable (je sais pas si ça se dit ). C’est comme le code cross platform de Windev Mobile: on en reparlera le jour ou le projet aura grossi et qu’il faudra le passer sur un autre OS.

    Q4-Oui, l’ajout d’un tiers apporte effectivement un inconvénient sur les perfs. Concernant les requêtes multi tables, tu ne le vois peut être pas encore si les tables sont petites, mais la conception de la base de données a une importance capitale sur les perfs de la base. Je te recommande de lire ça https://blog.developpez.com/sqlpro/p...mances_petites
    Le graphique en rouge/vert/bleu n’est pas qu’un graphique: je connais plusieurs applications qui en production, se cassent la figure pour les raisons expliquées dans ce lien.
    Par ailleurs, construire des requêtes optimisées pour les bases de données c’est un métier, qui n’est pas le nôtre à la base. Les développeurs raisonnent souvent en itératif (traiter un par un les éléments via les boucles) pour l’algorithmie, moi le premier, ce qui est une erreur lorsqu’on s’adresse à une base de données qui fonctionne de manière ensembliste (tout paralléliser et traiter d’un seul coup). Ca fonctionne certes, mais quand la base grossit les requêtes deviennent très lentes. J’ai parfois lors de mes R&D conçus des requêtes qui mettaient plusieurs secondes ou dizaines de seconde à s’exécuter, alors que formulées différemment au moteur de base de données, en 300 millisecondes c’était fini…une base bien conçue ne doit pas s’écrouler niveau perf même quand le volume augmente. Certes pour HFSQL, tu découvriras surement que ce n’est pas toujours forcément le cas…même si la base est plutôt bien conçue.

  10. #10
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    Citation Envoyé par kunnskap Voir le message
    Q1-Pour l’exemple je peux t’en proposer un mais je suis sous WD25, je dois savoir quelle version tu as sinon tu ne pourras pas ouvrir le projet. Je peux faire un zip en téléchargement car le code ne va pas rentrer dans un message de forum ça risque d’être compliqué à caser

    Pour ton autre question: sur un plan purement temporel, oui, l’objet a un impact. Ajouter de l’abstraction augmente le temps de traitement. Mais sur un plan pratique, on parle de millisecondes, ce sont des traitements processeur/mémoire. Donc le surcoût est finalement assez faible en comparaison de la maintenabilité plus facile à long terme et des possibilités que ça apporte. L’objet se paye en temps pendant les phases d’étude (la conception est plus lente) et en temps d’exécution sur le poste (qui est plus lente aussi) mais par la suite les avantages apparaissent.

    Pour les tables de jointure, je peux aussi te joindre un exemple mais ce n’est pas très grave c’est plutôt pour ce que tu dis en Q2 justement. Pour ta question sur l’ID Auto, un prestataire avec lequel j’ai bossé m’a donné la réponse.
    Dans tes rôles, un contact peut avoir plusieurs rôles et un rôle peut donc appartenir à plusieurs contact (de mémoire c’est ainsi que Windev le formule dans l’assistant). Quand tu veux modifier les rôles d’un contact tu as 2 solutions: supprimer tous les rôles existants dans la table de jointure et les recréer selon ton bon vouloir; ou supprimer les rôles inutiles et n’ajouter/modifier que les rôles nécessaires. Dans le premier cas, tes lignes sont supprimées, l’ID Auto avec donc. Dans le deuxième cas, l’ID Auto perdure puisque des lignes vont rester. Si tu choisis la première solution, inutile de mettre d’ID Auto puisqu’il ne servira qu’assez peu, les lignes se feront exploser régulièrement. Dans le deuxième cas, l’ID Auto a davantage d’intérêt puisqu’il reste dans la table et que le moteur pourra s’en servir pour optimiser ses requêtes si il veut.
    Pour les deux exemples, je suis également sous WD25

    Pour l'ID auto, j'utilise actuellement la 2eme solution mais je m'étais justement posé cette question, soit de supprimer toute les lignes et les recréer, soit de modifier uniquement les lignes à modifier, ajouter uniquement les lignes à ajouter et supprimer uniquement les lignes à supprimer. Je me demandais justement si c'était "propre" de tout supprimer (donc même les lignes où il n'y a ni modification, ni suppression) et de tout recréer, c'est pour cela que j'avais choisi la seconde solution, qui est sans doute moins facile que la première mais qui a l’avantage d’éviter de supprimer des enregistrements qui auraient pu être ajouté entre temps par un autre utilisateur. Je rajouterais donc un Id auto dans ces tables

    Citation Envoyé par kunnskap Voir le message
    Q3-Oui alors la webisation, attention hein. Je travaille sur des projets de plusieurs années, quasi entièrement en procédural, si tu arrives à Webiser des app comme ça en un clic avec le bouton tu m’appelles c’est une pub de PC SOFT ça. Si le code et l’IHM ne sont pas rigoureusement conçus, c’est inwebisable (je sais pas si ça se dit ). C’est comme le code cross platform de Windev Mobile: on en reparlera le jour ou le projet aura grossi et qu’il faudra le passer sur un autre OS.
    Oui non mais j'ai utilisé le terme "webiser" dans le sens de faire une version web du projet. En séparant les couches d'avance, cela sera probablement plus simple pour moi ensuite quand je ferais une version web que si je continue à mélanger le code UI et le code métier. Pour le bouton Wébiser de Windev à Webdev, je n'ai pas encore pu tester mais cela parait trop beau pour être vrai, sachant qu'eux même précisent qu'il faut au minimum 2h de modification par fenêtre pour wébiser, donc je ne m'attends pas à grand chose de ce bouton, même si je testerais probablement le moment venu

    Citation Envoyé par kunnskap Voir le message
    Q4-Oui, l’ajout d’un tiers apporte effectivement un inconvénient sur les perfs. Concernant les requêtes multi tables, tu ne le vois peut être pas encore si les tables sont petites, mais la conception de la base de données a une importance capitale sur les perfs de la base. Je te recommande de lire ça https://blog.developpez.com/sqlpro/p...mances_petites
    Le graphique en rouge/vert/bleu n’est pas qu’un graphique: je connais plusieurs applications qui en production, se cassent la figure pour les raisons expliquées dans ce lien.
    Par ailleurs, construire des requêtes optimisées pour les bases de données c’est un métier, qui n’est pas le nôtre à la base. Les développeurs raisonnent souvent en itératif (traiter un par un les éléments via les boucles) pour l’algorithmie, moi le premier, ce qui est une erreur lorsqu’on s’adresse à une base de données qui fonctionne de manière ensembliste (tout paralléliser et traiter d’un seul coup). Ca fonctionne certes, mais quand la base grossit les requêtes deviennent très lentes. J’ai parfois lors de mes R&D conçus des requêtes qui mettaient plusieurs secondes ou dizaines de seconde à s’exécuter, alors que formulées différemment au moteur de base de données, en 300 millisecondes c’était fini…une base bien conçue ne doit pas s’écrouler niveau perf même quand le volume augmente. Certes pour HFSQL, tu découvriras surement que ce n’est pas toujours forcément le cas…même si la base est plutôt bien conçue.
    Merci pour le lien, je vais lire tout ça. Je sais déjà qu'une de mes requêtes n'est pas optimisée, je récupère l'ensemble des contacts pour une société que j'affiche dans une table sur ma fiche société, avec le nom, prénom (table contact), le rôle par défaut (Nom) (donc table jointure Contact_Role, et table Role pour le nom), de même pour la fonction par défaut, le numéro de tél par défaut, l'email par défaut.. Je n'ai pas réussi à construire ma requête en un seul bloc, donc j'ai un Select pour récupérer les contacts de ma société, puis ensuite je parcours mes résultats en exécutant deux autres requêtes "SELECT TOP 1 ..." afin de récupérer la fonction et le rôle par défaut parmi la liste des rôles et fonctions du contact en cours dans ma boucle. De ce fait, il y a une latence non négligeable à l'affichage de cette table.

    Juste une question par rapport au table justement. J'ai dans mon projet une table "Moyen de communication" où je peux ajouter un type de communication (Tél fixe, mobile, mail etc), j'ai ensuite une table "Communication" liée qui reprends l'ID du type de communication de la table "Moyen de communication" et la valeur (06.., @, www. etc selon le type choisi) pour une société (table société - ID société) OU un contact (table contact - ID contact). En gros ma table "Communication" est une table jointure mais pas liée directement du côté société et du côté contact puisqu'on ne peut pas lier une même rubrique à deux tables différentes, ce qui est logique. J'ai donc une rubrique "Type d'entité" (1=contact, 2=société) et "ID entité" dans ma table "Communication". Les deux me permettant de différencier un ID contact d'un ID société. Cela fonctionne, mais le fait qu'il n'y ait pas de liaison entre Contact > Communication et Société > Communication me dérange un peu. L'autre solution est de créer une table jointure "Contact_Communication" et une table "Société_Communication". Dans ce cas j'aurais bien mes liaisons, mais j'aurais aussi deux tables identiques. Il y a sans doute également la solution d'avoir une rubrique "ID contact" et une rubrique "ID société" dans la table "Communication", l'un étant égal à 0 quand l'autre est renseigné, mais pas sur que cela soit plus propre. Quel est la meilleur solution selon toi ?


    En tout cas merci pour toutes tes explications

  11. #11
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    En fait ça va être simple pour ton exemple, dans le code des déclarations globales de la fenêtre je fais:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    oClient est un Mtbl_client dynamique <- allouer un Mtbl_client
    oTabClients est un tableau de Mtbl_client dynamique <- oClient.getAll()
    Evidemment, là c'est simpliste. Ca n'est pas forcément dans le code des déclarations globales que je vais remplir le tableau, je peux aussi le mettre dans le traitement de demande de mise à jour de l'affichage de la fenêtre.

    getAll() est une méthode de la classe Mtbl_client contenant par exemple ça:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    tabClients est un tableau de Mtbl_client
     
    maReq est une chaîne = "SELECT * FROM " + tbl_client..Nom
    maListeDeclients est une Source de Données
     
    SI PAS HExécuteRequêteSQL(maListeDeclients,MaConnexion,hRequêteSansCorrection,maReq) ALORS
    	Erreur(ErreurInfo())
    SINON
    	FichierVersTableau(tabClients,maListeDeclients)
    FIN
     
    RENVOYER tabClients

    tbl_client est ma table qui stocke les clients dans l'analyse. Avant, la méthode getAll() était globale, mais je ne voulais plus laisser de méthodes globales dans un objet car ça n'a pas de sens de faire de l'objet si on bombarbe le code de méthodes globales...autant passer en procédural. Si tu veux un tableau d'objets, tu en instancies un pour qu'il te retourne ce que tu veux et tu bénéficies de tous les avantages de l'objet.

    Pour l'ID Auto, l'une ou l'autre des solutions est arbitraire je dirais. Par facilité je vires tout et je recommence, cela concerne en général 10 ou 20 lignes tout au plus à gérer dans les cas que je traite. Après, ca peut aussi dépendre du nombre d'utilisateurs visés: si c'est une appli internationale, qu'on aura plus d'utilisateurs,peut être que c'est du coup judicieux de ne pas supprimer systématiquement les lignes pour ne pas fracasser l'index d'une table qui pourra être très grande. Mais là, ça sort de mon champ de compétences, c'est de la BDD.

    Dans tous les cas séparer les couches est préférable, mais en effet, le bouton Webiser est trop beau pour être vrai.

    De ce que je comprends de ta dernière question:
    -les solutions du style l'un est à zéro/vide quand l'autre est plein, interdit. Une base de données ne doit pas stocker de valeurs vides sur les lignes parce que la conception fait qu'une valeur est obligatoire quand l'autre ne l'est pas, c'est qu'il y a un souci de conception. De même, on ne stocke pas un ensemble de valeur dans une seule rubrique. Avoir Client.Rôles = "3;5;4", c'est pas bon du tout pour les perfs. Donc ta toute dernière solution pour moi c'est non
    -l'usage veut qu'on ajoute plutôt des tables, et pas des rubriques. Pour cette raison, et parce que je préfère qu'une table ne stocke les relations que d'un type d'entité, et qu'au dev ça sera plus simple à requêter, je ferais 2 tables de liaisons. Ce qui rend inutile la rubrique Type d'entité.

  12. #12
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    Bonjour,

    Merci pour l'exemple, j'ai bien pu avancer mais j'aurais encore besoin d'un petit coup de main car j'ai un souci, du moins je ne sais pas comment modéliser le genre de cas suivant :

    J'ai une table "Probleme" qui me permet d'ajouter les problèmes rencontrés avec les sociétés. Pour chaque "Probleme" je peux associer un ou plusieurs contacts. J'ai donc une table jointure entre ma table "Probleme" et ma table "Contact".
    La table en question :
    Probleme_Contact
    - PBid (ID probleme)
    - CONTid (ID contact)
    - PBCONTcom //Commentaire
    - PBid_CONTid (Clé composé unique)

    J'ai donc créé la classe correspondante à ma table jointure, avec les méthodes d'ajout/modif/suppression dans la bdd :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    MProbleme_Contact est une Classe <MAPPING=Probleme_Contact>
    hérite de MBase
    <MAPPING>
    m_nPBid		est un entier   <MAPPING=PBid>
    m_nCONTid		est un entier   <MAPPING=CONTid>
    m_sPBCONTcom 	est une chaine <MAPPING=PBCONTcom>
    <FIN>
    FIN

    Maintenant, quand j'ouvre ma fenêtre du problème sur le logiciel, j'ai donc mes champs lié à ma table "Probleme" qui sont renseignés via databinding sur ma classe Probleme. Là tout va bien cela fonctionne. Cependant dans cette même fenêtre j'ai une table visuelle qui m'affiche les contacts associés à ce problème. Dans cette table visuelle j'ai les colonnes suivantes : "COL_PBid, COL_CONTid, COL_CONTnom, COL_CONTprenom, COL_SOCid, COL_SOCnom, COL_PBCONTcom". Pour pouvoir remplir cette table, j'ai créé une requête (REQ_Sel_ProblemeContact) basée sur ma table jointure "Probleme_Contact" + ma table "Contact" afin de récupérer le nom "CONTnom", prénom "CONTprenom" et l'ID de la société "SOCid" du contact (via CONTid de ma table jointure) + ma table "Société" pour récupérer le nom "SOCnom" de la société (via SOCid de ma table Contact) associé au problème (Paramètre PBid).

    Cependant, dans ma classe MProbleme_Contact, je n'ai pas les membres CONTnom, CONTprenom, SOCid et SOCnom mais uniquement CONTid, de ce fait, je ne peux pas remplir un tableau de cette classe via le résultat de ma requête


    Question : Faut-il créer une classe basée et mappée sur ma requête (REQ_Sel_ProblemeContact) de la façon suivante : ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    MREQ_Sel_ProblemeContact est une Classe <MAPPING=REQ_Sel_ProblemeContact>
    <MAPPING>
    m_nPBid		est un entier   <MAPPING=PBid>
    m_nCONTid		est un entier   <MAPPING=CONTid>
    m_sPBCONTcom 	est une chaine <MAPPING=PBCONTcom>
    m_sCONTnom      est une chaine <MAPPING=CONTnom>
    m_sCONTprenom  est une chaine <MAPPING=CONTprenom>
    m_nSOCid           est un entier   <MAPPING=SOCid>
    m_sSOCnom        est une chaine <MAPPING=SOCnom>   
    <FIN>
    FIN
    Avec la requête dans une méthode GetAll(Parametre PBid) pour remplir un tableau de MREQ_Sel_ProblemeContact ?

    Si oui, lors du clic sur le bouton Enregistrer en bas de ma fenêtre problème, comment enregistrer dans ma table jointure "Probleme_Contact" les données (PBid, CONTid, PBCONTcom) qui sont présents dans mon tableau de ma classe REQ ? En effet, ma classe basée sur ma requête ne possède pas les méthodes d'enregistrements présent dans ma classe "MProbleme_Contact". Donc quand je vais parcourir chaque ligne/objet de mon tableau mémoire basé sur ma classe de ma requête, je ne pourrais pas enregistrer dans ma BDD.
    Faut-il, lors de la validation, que j'alloue pour chaque ligne de mon tableau mémoire de ma classe REQ un objet MProbleme_Contact (qui possède donc les méthodes d'enregistrement dans la bdd) et que je copie "un par un?" les membres qui m'intéressent de l'objet REQ vers l'objet MProbleme_Contact (PBid, CONTid, PBCONTcom) ? (Et ensuite appeler la méthode Enregistrer de ma classe MProbleme_Contact pour enregistrer en BDD)

    Ou ce n'est pas comme ça qu'il faut procéder ?

    (J'avais aussi pensé à créer une classe basée sur ma REQ avec "Herite MProbleme_Contact" et de rajouter uniquement les membres m_sCONTnom etc dans ma classe, vu que les membres m_nPBid, m_nCONTid et m_sPBCONTcom sont communs avec ma classe MProbleme_Contact et que j'ai besoin de ses méthodes, mais je ne sais pas si cela se fait, et si FichierVersTableau() remplira automatiquement les membres héritées via ma requête)


    Merci d'avance,
    Cordialement,
    Esteban

  13. #13
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    Plusieurs solutions possibles:
    -créer la classe basée sur la requête et la remplir comme tu le dis avec un getAll
    -créer une vue SQL dans la base de données et sa classe associée (ça, j’ai jamais essayé, mais ça donne une «*existence*» à la requête en base et tu la vois dans l’analyse je crois, et qui sait, tu pourrais vouloir la réutiliser ailleurs. C’est un peu mieux que la première solution mais attention, se documenter sur les vues SQL de HFSQL)

    Si tu débutes, la première solution sera plutôt pas mal mais ta classe ne sera pas mappée à un fichier de l’analyse. Attention donc si tu écris les requêtes en texte et que tu mets à jour l’analyse, le texte ne sera pas mis à jour, et tu risques d’avoir des erreurs à l’éxecution. Pour éviter ça, soit utiliser des variables de types requêtesSQL, soit composer les requêtes (lancées par HExecuteRequêteSQL() ) à la main via les propriétés ..Nom des tables et des rubriques (alors oui, c’est long, c’est chiant, mais en cas de modif de l’analyse le compilateur va hurler en cas de problème).

    Pour Enregistrer, bah oui, ta méthode est pas mal…il faut bien que tu enregistres.à un moment les éléments. Si tu peux, évites de requêter plusieurs fois la base, car la mise à jour de la table Problème_contact peut se résumer en 2 requêtes: effacer tous les contacts, les rajouter via une seule requête INSERT INTO. L’avantage de la classe c’est qu’elle implémente le traitement que tu veux mais ne dois pas dépendre d’un élément de l’IHM; assures toi donc que ses paramètres ne soient pas des champs mais par exemple, un tableau associatif dans lequel la clé est l’ID du problème (clé unique dans le tableau associatif) et la valeur l’ID du contact. Certes avant d’appeler la fonction tu vas devoir composer ton tableau associatif et l’envoyer à la fonction qui va le sauvegarder d’un coup, mais c’est un moindre mal. A un moment oui, va falloir le coder….

    Ton dernier paragraphe est une bonne idée et une bonne utilisation du concept de l’héritage. FichierVersTableau fait un mapping champ-champ si les noms correspondent, mais pour savoir si ca le fait avec les membres hérités….va falloir le tester. Fais un petit projet à part avec des tables de test pour le checker. Je ne connais pas le projet dans sa globalité donc je ne peux pas dire si ça fonctionnera juste sur un forum mais sur le principe ça me va.

  14. #14
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    Merci pour ta réponse, entre temps j'avais bricolé une autre possible solution qui fonctionne également, par contre je ne sais pas si celle-ci est propre, du moins si ça se fait normalement :
    - Rajouter dans la classe MProbleme_Contact : m_pclContact est un MContact dynamique
    - Rajouter dans la classe MContact : m_pclSociete est un MSociete dynamique
    - Créer la méthode pour récupérer un tableau basée sur ma requête directement dans la classe MProbleme_Contact :

    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
    PROCÉDURE RecupereContactDuProbleme() <métier>
    tabContactProbleme est un tableau de MProbleme_Contact
     
    HExécuteRequête(REQ_Sel_ProblemeContact,hRequêteDéfaut,m_nDRPBid)
    POUR TOUT REQ_Sel_ProblemeContact 
    	clUnContact est un MProbleme_Contact
    	WL.FichierVersMémoire(clUnContact,REQ_Sel_ProblemeContact)
    	clUnContact.m_bNouveau					= Faux 
    	clUnContact.m_pclContact					= allouer un MContact
    	clUnContact.m_pclContact.m_sCONTnom			= REQ_Sel_ProblemeContact.CONTnom
    	clUnContact.m_pclContact.m_sCONTprenom		= REQ_Sel_ProblemeContact.CONTprenom
    	clUnContact.m_pclContact.m_pclSociete			= allouer un MSociete
    	clUnContact.m_pclContact.m_pclSociete.m_sSOCnom	= REQ_Sel_ProblemeContact.SOCnom
    	TableauAjoute(tabContactProbleme,clUnContact)
    FIN
     
    RENVOYER tabContactProbleme

    Le seul inconvénient ici c'est que je dois parcourir ma requête et faire un FichierVersMemoire() au lieu de Fichierverstableau() (Risque d'être moins performant?), et il faut que j'alloue un MContact et un MSociete afin de remplir les membres "CONTnom" "CONTprenom" "SOCnom". D'ailleurs est-ce que ça se fait d'allouer un MContact (classe qui contient ~20 membres) et un MSociete (classe qui contient ~20 membres) dans le seul but de remplir manuellement un ou deux membres sans remplir les autres vu que j'en ai pas besoin ?

    L'avantage par contre c'est que tout se fait sur ma classe mappée sur ma table de l'analyse, je n'ai pas besoin de créer une classe spécifique à ma requête et donc ensuite de devoir recopier un par un les éléments de ma classe requête vers ma classe MProbleme_Contact. Car j'avais essayé cette ligne de code "gpclContactProbleme <= gpclReqContactProbleme" afin de copier tous les membres de même nom dans mon objet MProbleme_Contact, mais cela m'a renvoyé une erreur du fait qu'il n'est pas possible de faire une copie homonymique quand la classe source et la classe destination possèdent tout deux des membres avec des attributs "MAPPING".

    Pour enregistrer en BDD, je peux ainsi directement créer une méthode dans ma classe MProblemeContact avec comme paramètre un tableau de MProbleme_Contact, je passe ensuite mon tableau d'objets MProbleme_Contact dans ma méthode et je fais un traitement pour pouvoir insérer en BDD via un INSERT INTO ?

    Qu'en penses-tu ?

    Pour information, concernant mon dernier paragraphe, j'ai testé, fichierverstableau() fonctionne également pour les membres hérités.

  15. #15
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    Ta solution est bonne sur le principe. Les attributs de chaque structure/classe basés sur un fichier doivent correspondre selon moi aux types présents dans l’analyse; mais rien n’empêche à côté de renseigner des objets tiers liés à la table que la structure/classe modélise.

    Effectivement la solution est moins efficiente car tu dois parcourir les lignes de la requête une par une. Pour le fait d’instancier un objet pour remplir 2 variable sur 20, on va dire que les 18 autres c’est pas ce qui prend le plus de place en mémoire.

    L’une des façons de faire autre c’est de faire travailler le moteur de base de données avec des JOIN; mais je ne connais pas bien ton niveau. C’est pas une solution simple à mettre en oeuvre quand on débute.
    C’est ce que je fais moi dans mes objets sur certains projets: en requêtant une seule fois, je peux obtenir la liste de tous les contacts d’un probleme avec leurs nom prénom via des JOIN. Ensuite je mappe les différents éléments dans la classe et les objets liés en faisant de l’introspection sur les variables.

    Je peux difficilement te donner des solutions de plus haut niveau sans connaitre exactement ton niveau, mais en progressant tu dois apprendre à diminuer le nombre de requêtes simples faites au moteur, au profit de requêtes plus complexe mais moins nombreuses. Evidemment tu dois adapter le code derrière.
    Quand le code bosse, c’est en général plus long à traiter que quand la base de données bosse (si bien sur, tu as correctement dit à la base de données ce que tu voulais)

    Pour l’enregistrement en BDD c’est en gros la solution que j’ai donné dans mon précédent message. Le but c’est que tu n’ai pas autant de requêtes à faire que de contacts à inscrire pour le problème, sinon tu perds en performances.

  16. #16
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    C'est justement ce que je cherche à faire, de pouvoir mapper les éléments dans ma classe ET les objets liés d'un coup et automatiquement plutôt que devoir allouer un objet Contact, et un objet Société pour remplir le nom et prénom du contact et sa société.

    Quand tu parles d'exécuter une requête avec des JOIN afin de récupérer tous les contacts d'un problème avec leur nom prénom et société, tu parles de ce genre de requête ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    sMaChaine est une chaîne = 
    [
    	SELECT Probleme_Contact.PBid AS PBid,Probleme_Contact.CONTid AS CONTid,Probleme_Contact.PBCONTcom AS PBCONTcom,Contact.CONTnom AS CONTnom,Contact.CONTprenom AS CONTprenom,Societe.SOCnom AS SOCnom
    	FROM Probleme_Contact
    	INNER JOIN Contact ON Probleme_Contact.CONTid = Contact.CONTid
    	LEFT OUTER JOIN Societe ON Contact.SOCid = Societe.SOCid
    	WHERE Probleme_Contact.PBid = '%1'
    ]
     
    sdMaReq est une Source de Données
    sMaChaine=ChaîneConstruit(sMaChaine,1) //Problème ID 1
     
    HExécuteRequêteSQL(sdMaReq,sMaChaine)
    Ma requête ici me retourne bien d'un coup tous les contacts pour un problème donné, avec leur nom et prénom et leur société s'ils en ont une

    C'est surtout ensuite pour affecter automatiquement mes objets liés à ma classe, de les faire matcher automatiquement avec un Fichierverstableau() sans avoir à allouer un MContact et d'affecter manuellement le nom et prénom retournés par ma requête. Tu parles d’introspection sur les variables qui visiblement permettrait d'automatiser tout le mapping par rapport à ma requête, je ne connais par contre pas cette méthode. J'ai essayé d'en savoir un peu plus mais il y a très peu d'exemple sur windev sur cette pratique et encore moins dans ce cas de figure, ça n'aide pas. J'arrive cependant à récupérer la définition d'une classe par programmation, les variables qui l'a composent

  17. #17
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    Ta requête m'a l'air bonne comme ça mais vérifies bien les différentes jointures possibles.

    De mémoire car ça date de plusieurs mois, j'avais des classes contenant des attributs qui étaient des objets (et pas dynamiques, car il faut que les objets soient alloués pour que le match fonctionne avec FichierVersMémoire, je n'utilisais pas FichierVersTableau)

    Quand la requête arrivait je me rendais compte que le FichierVersMémoire n'attribuait en fait pas les valeurs des membres des sous objets quand ces membres ne faisaient pas partie de l'objet d'origine visé par le FichierVersMémoire (un truc du genre un peu tordu, en gros ils n'attribuait que les membres de même nom dans les sous objets)

    Donc j'ai entrepris de l'introspection pour faire des FichiersVersMémoire successifs sur tous les sous objets de l'objet de départ (la profondeur étant infinie en théorie mais bloquée en pratique par la récursivité de la fonction de parcours, un peu dans le même genre que l'exemple que PC SOFT fourni pour parser un JSON). Comme tout se fait en mémoire, les perfs étaient très très bonnes.

    L'introspection c'est pas simple et c'est une mécanique à penser, un coup à prendre. C'est un atout parfait du polymorphisme car le programme se met à manipuler n'importe quel type d'objet sans s'en rendre compte et ça permet de grosses factorisations de code.
    La fonction récursive peut s'écrire ainsi. Je te donne pas toute la solution, mais avec ça ça devrait déjà bien t'avancer...après faut chercher. La fonction prend en paramètre un objet dynamique (oObjet) et va parcourir tous les sous objets.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    def est une Définition = RécupèreDéfinition(oObjet)
    FichierVersMémoire(oObj,NomSource)
     
    POUR TOUT Var DE def..Variable
    	SI Var.Définition..Type = wlInstance ALORS //Est un objet fils de l'objet principal
    		defObj est une Définition = RécupèreDéfinition(defObj)
    		FichierVersMémoire({"oObjet."+Var..Nom,indVariable},NomSource)
    		_recursiviteParcours({"oObjet."+Var..Nom,indVariable})
    	FIN
    FIN
    Dans ce cas à chaque fois elle fait un FichierVersMémoire mais tu peux envisager beaucoup d'autres traitements. Je m'en servais pour requêter automatiquement en base des tableaux. En une ligne je pouvais instancier un client, tous ses objets liés par des JOIN, tous les tableaux qui lui étaient liés (ses factures, ses adresses par exemple), et toutes les requêtes étaient générées par code automatiquement et matchées par la fonction ci dessus. Le programme était intelligent: quand il trouvait un tableau il requêtait son contenu en "devinant" le type de l'objet et la table à requêter avec les bons critères.

  18. #18
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    Salut,

    Merci pour ta réponse et sorry pour le temps de réponse. J'ai pu tester ta fonction récursive et en comprendre le principe. Si j'ai bien compris, ta fonction récursive rend automatique le traitement en vert suivant, pour mon cas où je dois remplir l'objet, le sous objet Contact et le sous sous objet Société :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    ExécuteRequête(REQ_Sel_ProblemeContact,hRequêteDéfaut,m_nDRPBid)
    POUR TOUT REQ_Sel_ProblemeContact 
    clUnContact est un MProbleme_Contact
    WL.FichierVersMémoire(clUnContact,REQ_Sel_ProblemeContact) //Objet
    WL.FichierVersMémoire(clUnContact.m_pclContact.,REQ_Sel_ProblemeContact) //Sous objet
    WL.FichierVersMémoire(clUnContact.m_pclContact.m_pclSociete,REQ_Sel_ProblemeContact) //Sous sous objet
    TableauAjoute(tabContactProbleme,clUnContact)
    FIN
    Cependant, si dans mon sous sous objet société, j'ai encore d'autres sous objets, la fonction récursive va parcourir tout les objets, sous objet, sous sous objet etc et faire des fichierversmémoire tant qu'il trouve un sous objet ? Il risque donc d'y avoir beaucoup de FichierVersMemoire inutile, risque de perte de performance ?

    De plus, il faut comme tu dis avoir des objets déclarés non dynamiques, hors je risque de ne pas avoir besoin d'instancier ces sous objets dans la plus part des cas, et je risque d'en avoir pas mal pour certains objets. S'ils sont instanciés par défaut à l'instanciation de mon objet principal, cela peut-il également poser des problèmes de performance ?
    Après je suppose qu'en adaptant la fonction récursive, il doit être possible d'allouer les objets avant de faire le fichierversmémoire() dans le cas où on a déclarer les sous objets comme dynamique ? Reste cependant ma question du dessus sur les fichierversmémoire "infini"


    J'ai par ailleurs recréé un nouveau topic, plus spécifique à la POO et ce que je souhaite faire, avec d'autres questions qui pourront peut-être servir pour ceux que ça intéresseront plus tard. Là le topic de base n'avait plus vraiment de rapport vu que je parlais de programmation procédurale et structures, donc niveau recherche c'était pas top :
    https://www.developpez.net/forums/d2.../#post11365113


    Bonne soirée,

    Cordialement

  19. #19
    Membre averti
    Homme Profil pro
    Chef de projet
    Inscrit en
    mars 2017
    Messages
    193
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Chef de projet
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : mars 2017
    Messages : 193
    Points : 404
    Points
    404
    Par défaut
    En effet, ma fonction découvre toute seule les sous objets et les remplis avec la requête.
    Elle parcours en effet toute la profondeur, jusqu’à ce que la limite de récursivité du WLangage soit atteinte cela dit; et je n’ai encore jamais vu d’objet aussi gros.
    Au niveau des perfs j’ai eu l’occasion de faire plusieurs tests, et malgré tout, c’est extrêmement rapide puisque totalement fait en mémoire et sans accès disque. Donc c’était quasiment imperceptible. Mais évidemment comme je le disais, l’objet apporte des abstractions, permet du polymorphisme, ce qui factorise du code, tout ça se paye.

    Si ton objet n’est pas dynamique il est instancié et donc je pense que les traitements du constructeur sont exécutés. Si ils sont nombreux, tu perdras en performance selon ces traitements, mais l’instanciation elle même ne consomme pas énormément de perfs c’est négligeable. Par contre, si y’en a beaucoup, la conso en RAM va augmenter (mais là encore, pas de quoi affoler tes 8 Go de RAM )
    Allouer les objets avant le fichierversmémoire, je dirais non, car la fonction TypeVar (https://doc.pcsoft.fr/fr-FR/?3013056...pevar_fonction) qui me donne la liste des types de variables possibles, liste wlInstance, mais il n’y a rien pour les objets dynamique. Donc si on peut pas identifier que l’objet soit dynamique on peut pas l’instancier à la volée, et même si on le pouvait, déterminer le type de la variable ne permet pas de déterminer la classe à instancier, à creuser du côté du type Définition voir si ça permet de récupérer le nom de la classe.

    Je vais voir le nouveau topic.

  20. #20
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    janvier 2020
    Messages
    37
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Aisne (Picardie)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : janvier 2020
    Messages : 37
    Points : 23
    Points
    23
    Par défaut
    J'ai fait quelques tests afin d'allouer le sous objet avant le fichierversmémoire. On peut identifier un objet dynamique. wlInstance fonctionne pour les deux types d'objets. Il suffit alors de faire un test en plus pour savoir si l'objet est "null". Si c'est le cas on sait que l'on est sur un objet dynamique et dans ce cas il suffit de récupérer le nom de la classe afin d'instancier l'objet. C'est de ce coté que ça coince, car pour récupérer la définition de l'objet il faut qu'il soit instancié, j'ai essayé plusieurs fonctions, dont :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    def est une Définition = RécupèreDéfinition(oObjet)
    m est une Description de Variable
    m = def..Variable[{"pclMonObjet."+Var..Nom,indVariable}]
    Info(m..nom)
    mais vu qu'il n'est pas instancié, cela ne fonctionne pas.


    Le seul moyen que j'ai trouvé pour pouvoir instancier l'objet, c'est de créer un tableau global associatif que je remplie à l'ouverture du logiciel, avec en clé le nom du sous objet et en valeur la classe correspondante. Par exemple pour reprendre mon cas, je dois instancier m_pclContact (Classe MContact) dans ma classe MProbleme_Contact et m_pclSociete (classe MSociete) dans ma classe MContact.

    J'ai donc dans mon tableau associatif global :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    gtaClasseAInstancier est un tableau associatif de chaînes
    gtaClasseAInstancier["m_pclContact"]	= "MContact"
    gtaClasseAInstancier["m_pclSociete"]	= "MSociete"
    //Ajouter d'autres sous objets avec leurs classes respectives afin de les instancier automatiquement lors du parcours de la fonction récursif
    et je le passe en paramètre dans ma fonction récursive :
    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
     
    PROCÉDURE RecursifMapping(pclMonObjet est un objet dynamique,sMaSource est une chaîne,taClasseAInstancier est un tableau associatif de chaîne) 
    def est une Définition = RécupèreDéfinition(pclMonObjet)
    WL.FichierVersMémoire(pclMonObjet,sMaSource)
     
    POUR TOUT Var DE def..Variable
    	SI Var.Définition..Type = wlInstance ALORS //Est un objet fils de l'objet principal
    		SI {"pclMonObjet."+Var..Nom,indVariable} = Null ALORS //Si l'objet fils n'est pas instancié
    			soit MaClasse = taClasseAInstancier[Var..Nom] //On recherche la classe à instancier dans le tableau associatif via le nom de l'objet fils
    			SI MaClasse <> "" ALORS 
    				{"pclMonObjet."+Var..Nom,indVariable} <- allouer un MaClasse //On instancie l'objet fils
    			SINON
    				CONTINUER
    			FIN
    		FIN		
    		WL.FichierVersMémoire({"pclMonObjet."+Var..Nom,indVariable},sMaSource)
    		RecursifMapping({"pclMonObjet."+Var..Nom,indVariable},sMaSource,taClasseAInstancier)
    	FIN
    FIN
    Et là l'instanciation fonctionne. Le fait d'avoir un tableau associatif me permet également d'instancier automatiquement certains objets et pas d'autres (s'il ne trouve pas, la fonction n'instancie pas et passe au suivant) mais présente aussi des inconvénients...


    Après c'est étonnant qu'on ne puisse pas récupérer simplement le nom de la classe de l'objet fils s'il n'est pas instancié (Doit bien y avoir un autre moyen) et d'ailleurs je ne comprends pas non plus qu'on soit obligé de préciser obligatoirement la classe quand on alloue. Si dans mon objet, j'ai créé un objet fils m_pclContact est un MContact dynamique, je devrais pouvoir l'instancier "allouer m_pclContact" ou "m_pclContact <- Allouer" sans préciser la classe vu qu'elle est précisée dans la classe mère que c'est un objet fils MContact.

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Question sur l'architecture trois tiers
    Par sheridan08 dans le forum Plateformes (Java EE, Jakarta EE, Spring) et Serveurs
    Réponses: 10
    Dernier message: 19/06/2013, 15h10
  2. [Débutant] [Question] Architecture n-tiers et patern MVVM
    Par quentin869 dans le forum C#
    Réponses: 5
    Dernier message: 14/06/2013, 20h16
  3. Génération de code, architecture 3 tiers et databinding avancé
    Par neo.51 dans le forum Général Dotnet
    Réponses: 56
    Dernier message: 25/11/2008, 13h05
  4. Question de pointeur entre un programme et une DLL
    Par Neilos dans le forum C++Builder
    Réponses: 12
    Dernier message: 01/02/2005, 20h12
  5. [Design Patterns] Architecture 3 tiers
    Par HPJ dans le forum Design Patterns
    Réponses: 1
    Dernier message: 29/07/2003, 12h49

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