none
ObjectDataSource et Composant externe RRS feed

  • Question

  • Bonjour à tous,

    Je suis confronté actuellement à un problème...
    Je travaille à la migration d'une application Client Lourd / Serveur développée en Powerbuilder vers une architecture 3-tiers basée sur :
    - 1 couche BdD Oracle,
    - 1 couche de composants métiers développées en Powerbuilder 11
    - 1 couche présentation Web ASP.NET ou WinForm C# (actuellement Web, Winform dans le futur)

    Je présente tout d'abord le contexte:
    Je créé mes objets métiers en PB. Ces objets crééent une transaction, récupèrent les données de la base Oracle dans une objet datastore propre à PowerBuilder, et possèdent quelques méthodes type nbLignes()... . Je les déploie sous forme d'assembly .NET, puis les référence dans l'environnement Visual Studio. J'instancie ensuite ces objets (composants) dans mon code C#, puis appel les méthodes de ces objets. Jusque là, tout fonctionne correctement. Les objets s'instancient, crééent la transaction, et je récupère également le bon nombre de lignes.

    Je souhaiterais maintenant lier ces composants à un objet visuel .NET de type GridView. Il m'a semblé que le meilleur moyen de faire celà est de passer par un ObjectDataSource. J'aimerais trouver un moyen "automatique" pour lier un composant PB avec un ObjectDataSource, via des méthodes à implémenter, afin que l'ObjectDataSource soit capable de récupérer les données sans que j'ai à utiliser un autre type de collection que celui que j'utilise en interne à mon objet PB.

    Actuellement, le paramètre SelectMethod de l'ObjectDataSource necessite que la méthode en question retourne un DataSet, un DataReader, ou un objet implémentant l'interface IEnumerable.
    2 Problèmes:
    - 1) dans l'environnement PB, je ne peux pas créer une méthode qui retourne un objet de type DataSet, ou un quelconque objet d'un type propre à .NET, et ce même si j'intègre les bonnes librairies du framework. Je ne peux donc pas créer mon composant PB contenant une méthode retournant le bon type de collection (je ne peux pas non plus implémenter d'interface).
    - 2) Si je créé un DataSet dans mon code C#, et que je le rempli ligne par ligne en récupérant les données de mon composant PB via des méthodes personnalisées PB, et que je lie ensuite ce dataset à mon ObjectDataSource, cela fonctionne, mais cela m'oblige à faire une étape intermédiaire... Remplir un un nouvel objet ligne par ligne, alors que j'ai déjà récupéré les données, je ne trouve pas çà très optimisé, et dommage!

    Existe-t-il donc un moyen de lier un objet métier à un ObjectDataSource SANS utiliser une collection .NET? Par exemple définir un certain nombre de méthodes dans mes objets métiers retournant des types simples:
    - une méthode pour retourner le nombre de colonnes,
    - une méthode pour retourner les noms de colonne,
    - une méthode pour retourner l'information du champs (i,j) (i = ligne, j = colonne)...
    - une méthode pour passer à la ligne suivante...

    Ce n'est qu'un exemple, mais existe-t-il ce type d'interface à implémenter ?? J'ai vu quelquechose comme le IEnumerator, mais je n'ai pas compris ce que c'était exactement, ni comment l'utiliser... La méthode current() de cet objet retourne un type "object", et je ne sais pas faire le lien entre un "object" et une donnée de mon objet métier... J'crois que je me mélange les pinceaux.

    Il faut peut-être que je redéfinisse un objet héritant de objectdatasource, mais je dois bien avouer ne pas savoir par où commencer!

    Voilà! J'espère avoir été clair... Désolé si ce n'est pas le cas. Désolé aussi si ma demande ne se situe pas dans le bon forum.
    Merci d'avance.

    mardi 11 septembre 2007 09:45

Réponses

Toutes les réponses

  • Votre 2ème solution même si vous la trouvez "pas optimisé" est la solution conseillée...

    Ce n'est pas à la couche métier de formater les données en fonction de l'IHM choisi (dans un DataSet, dans un tableau, dans un dictionnaire,...etc). Si vous faites de la sorte, vous êtes en train de programmer une couche métier qui s'occupe de renvoyer des données à la fois sémantique et technique (la façon de représenter les données) !

     

    Par exemple, si votre couche métier renvoie des données au format UTF-8, c'est à la couche IHM de récupérer ces données et de les formater dans l'encodage qui doit être utilisée pour les fenêtres, pages,...etc

     

    Si vous commencez à implémenter les interfaces .NET qui permettent la liaison de données métier aux objets IHM, il faudra aussi implémenter (pour plus-tard) les interfaces pour les IHM JSP, PHP,...etc. Cela n'a aucun sens et rend votre couche métier dépend d'une (ou plusieurs) technologie... Il faut penser aussi tests ! Il faudra tester toutes vos règles métier pour tous les IHM ! La couche métier est quelque chose de 100% "fonctionnel", elle prend des données et renvoie des données dans un unique format (propre à la technologie utilisée pour programmer la couche métier). Après c'est à la couche IHM (et accès aux données d'ailleurs) de ce débrouiller pour utiliser (ou remplir) les données métier dans le bon format... Bien sûr on essaye de renvoyer des^données dans un format le plus standard possible afin de viser un maximum d'IHM (par exemple Unicode).

     

    Imaginez ce cas : vous livrez l'executable métier et bdd avec une belle documentation à une filiaire de votre entreprise qui utilise vos données. Vous n'avez aucune connaissance de l'IHM qui sera utilisée (ce qui est normale), ce que va faire cette société :

    C'est lire la documentation, regarder la méthode CalculTVA() qui prend en paramètre (par ex) un libellé, un flottant normalisé "IEEE machin" et qui renvoi un flottant normalisé "IEEE machin". Si cette société utilise une technologie IHM qui ne supporte pas le "IEEE machin" mais le "IEEE machinV2", c'est à elle de se débrouiller de trouver (ou programmer) une petite méthode qui se charge de convertir ces types de flottant...

     

    J'espère que vous comprenez ce que je veux dire par cette exemple...

     

    Vous devez donc créer des "wrapper" dans votre IHM .NET qui s'occupent de convertir des objets PowerBuilder vers des objets .NET exploitable par le DataBinding... Il ne faut surtout pas modifier des objets métier de PB pour orienter et faciliter le développement en .NET, sinon vous allez axer votre couche métier vers des IHM .NET...

     

    A noter que si vous utiliser ASP .NET et WinForms, pensez à créer une "bibliothèque wrapper" commune pour vous 2 applications !

     

    Cela peut vous "paraitre lourd", mais dans de gros projet c'est que l'on fait...

     

    D'expérience j'ai eu ce genre de situation ou j'ai livré une partie métier en .NET a 5 clients, l'un des 5 développait des IHM en Windev, il m'a téléphoné en me disant qu'il avait des problèmes d'interface avec .NET/COM/Windev, je lui ai dis qu'il n'avait qu'à mettre Windev à la poubelle et utiliser autre chose... Il a choisi Java et PHP et n'a pas eu de problème pour utiliser ma couche métier. Tout celà pour vous dire que je n'allais pas modifier ma couche métier pour créer des "méthodes spéciales à Windev"... Cela m'aurait obligé à modifier pas mal de chose et à tester en double ma couche métier...

     

    Cordialement

     

     

     

    mardi 11 septembre 2007 10:55
    Modérateur
  • Tout d'abord, merci de votre réponse rapide!

    Je pense comprendre les principes à appliquer. Il faut que je trouve un format "standard" (XML, tableau...), compatible avec un typage simple, et compréhensible par toute couche d'IHM pour transférer les données de PowerBuilder à .NET. C'est ensuite dans ma couche IHM que j'implémenterai les méthodes pour lire ce format, et remplir une collection de type datatable.

    Mais il faut quand même que je trouve un moyen "générique" pour transférer mes données vers .NET. Je suis obligé de les "formater" d'une certaine manière, puisque .NET sera incapable de lire directement dans le datastore PowerBuilder instancié dans mon objet métier PB.

    Peut-être que vous ne connaissez pas les possibilités de Powerbuilder, mais au vu des possibilités qu'offre .NET, quelle est selon vous la meilleure méthode à appliquer (en terme de simplicité de mise en oeuvre, de performance...). Par exemple, est-il préférable d'utiliser un format XML pour faire ce lien (sous forme de fichier, sous forme de chaine?), des tableaux de structures (les structures font partie des "types simples" pour le marshalling/demarshalling, mais je n'aime pas cette solution, car je ne peux pas construire dynamiquement des structure en PB), ou une autre idée ? Sachant également que les données pourront être de l'ordre de plusieurs milliers de ligne. Est-il interessant de passer par un support intermédiaire, comme des fichiers, plutot que d'utiliser directement des retours de fonction?

    Je ne sais pas quel est le meilleur format... C'est assez dépendant, j'imagine, de la performance des méthodes qui le lise, non ?


    Merci encore!
    mardi 11 septembre 2007 11:39
  • Comment accédez à votre couche métier dans une application .NET ? Appel de DLL ? Utilisation de COM ?

     

    Il est vrai que l'XML est un format d'échange très "interropérable" mais nécessite beaucoup d'effort à mettre en oeuvre et peut devenir pénalisant en terme de performance pour l'échange de grosse quantité de données.

     

    Pour vous proposer une solution qui pourrait correspondre à votre besoin, il faudrait nous détailler précisement la façon dont vous avez accès à votre couche métier via .NET...

     

    Cordialement

     

     

    mardi 11 septembre 2007 13:40
    Modérateur
  • Ok.
    Je construis mes objets métier à l'aide Powerbuilder 11. Dans leur constructeur, j'initialise une transaction, créé un datastore ("collection" powerbuilder), effectue une requete et charge les données. Je peux ensuite créer des méthodes pour cet objet métier, tant que celles-ci retournent des types "simples" : int, long, ..., structures, tableaux, et objets métier eux même. Idem pour les attributs et leur visibilité (private, public, protected).

    Jusque là, rien de transcendant. Tout çà pour dire que mon lien vers la base de données se fait bien dans mes objets métiers, que c'est Powerbuilder qui s'en charge, et que Powerbuilder est un environnement de développement objet tout ce qu'il y a de plus classique (au cas où vous ne connaitriez pas cet IDE). L'utilisation de Powerbuilder reste necessaire. Ce n'est pas notre objectif pour l'instant de migrer la couche métier dans une autre technologie.

    Je déploie ensuite ces objets métiers sous forme d'assembly .NET. Ce sont donc des DLL qui sont générées. Je référence (importe) ces dll dans l'environnement Visual Studio. Je suis en mesure ensuite d'instancier et d'utiliser ces objets comme s'ils faisaient partie intégrante de l'application. La technologie utilisée est alors, si je ne m'abuse, une technologie COM encapsulée sous forme d'assembly (il me semble avoir lu ceci quelquepart). Tout est transparent, je ne gère pas moi même la communication COM, je laisse faire le framework.

    Peut-être que je me trompe quant à la façon dont tout cela fonctionne, dans les concepts (si c'est bien de COM dont on parle), mais c'est bien ce que je fait.
    Pas de webservices pour l'instant (possibilité d'évolution vers une architecture SOA dans le futur).

    Cette réponse vous convient-elle? Avez-vous besoin de renseignements supplémentaires (exemples de code...)?

    Merci pour votre patience.
    mardi 11 septembre 2007 14:30
  • Merci pour détail, pour information c'est bien du COM...

     

    Je vous conseille dans ce cas de faire un wrapper vers des objets .NET... Pour 3 raisons :

    • Même si .NET s'occupe de la gestion des objets COM, il se peut que vous ayez besoin d'appelez des méthodes propre à vous pour libérer des ressources... Ces méthodes ne sont pas à appeler dans du code de présentation (cela couple trop la gestion des objets COM à l'IHM .NET).
    • Certains objets en .NET comme le DataTable, possède (en plus des implémentations du DataBinding) des méthodes permettant de filtrer, trier,...etc des données.
    • Utiliser directement vos objets COM avec un IHM .NET rend votre code "trop dépendant" et étroitement lié à votre DLL PwB. (Un petit changement dans cette DLL affecte tout votre ou vos IHM..., tandis qu'avec un wrapper vous aurez le contrôle total de ces changements...)

    Maintenant il faut savoir que créer un wrapper signifie "du code" et donc de bugs et qui n'apporte rien de plus dans le fonctionnel de votre application.

    Cependant une fois que vous aurez créer votre wrapper, vous pourrez l'utiliser dans toutes les plates-forme .NET (WebServices, ASP. NET, WinForms, Mobile,...etc).

     

    Si vous souhaitez utiliser directement vos objets COM, il vous faudra passer par un wrapper très light qui se compose tout simplement de collection de vos objets (tableau, liste,... bref tout ce qui implémente IEnumerable). Cela vous permettra de lier ces objets dans un ObjectDataSource (ou BindingSource pour WinForms).

    Une telle méthode offre très peu de code, une rapidité de développement, mais vos objets métier sont directement visible par l'IHM... Et comme je l'ai dis précédement, lors de la modification d'un objet cela aura un impacte direct sur votre IHM (C'est à dire une page, une fenêtre,...etc).

     

    Est-ce que vous pouvez me fournir un lien hypertexte officiel concernant la documentation du DataStore ?

     

    Je n'ai jamais eu ce genre de choix à prendre en ce qui concerne l'utilisation d'une couche métier d'une autre technologie. Mais sachez que lorsque j'utilise du COM (Word, Excel et le Fax de Windows), je me créer toujours une "bibliothèque wrapper" qui s'occupe d'utiliser les objets COM. Je pars toujours du principe que je ne veux pas que mes applications soient trop lié avec ces objets COM... Ce n'est pas à mes applications de gérer les ressources, erreurs,...etc propre à ces objets COM...

     

    Cordialement

    mardi 11 septembre 2007 15:40
    Modérateur
  • Re bonjour,

    Voici le lien vers la documentation en ligne PB:
    DataStore object
    Si cela ne fonctionne pas (pas sûr de leurs URL):
    http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.dc37783_1100/html/dwref_merged/title.htm&toc=/com.sybase.help.pb_11.0/toc.xml
    Puis aller dans
    --> Objects and Controls
    --> System Object Properties, Events, and Functions
    --> Datastore Object

    Je ne suis pas sûr de comprendre exactement ce que vous appelez "wrapper" ? Il me semblait que cela consistait en un certain nombre de méthodes permettant d'adapter une certaine "forme" d'objet vers une autre "forme", pour les rendre compatibles.
    En fait, ce serait plutot une "couche" à part entière?
    Je comprend le fait de ne pas trop lier la gestion des composants COM à mon IHM, mais j'ai du mal à me représenter ce que peut changer une couche intermédiaire, et comment la concevoir... Cela n'est pas juste de l'encapsulation? Pouvez-vous me donner un exemple?

    Je suis en mesure de vous donner davantage d'informations concernant mes objets métiers, ou powerbuilder en lui même.

    J'ai encore une question...
    Si l'on ne regarde QUE l'affichage des données, pensez-vous qu'il faille construire n objets métiers, correspondants à mes classes de mon diagramme de classe, contenant chacun l'execution d'une requête SQL de récupération des données? Ou est-il préférable d'avoir un type d'objet particulier, propre à la récupération de données, qui s'initialisera différamment en fonction de ce que je souhaite afficher (la requête SQL serait alors écrite dans la partie .NET, et transmise en paramètre au constructeur)? A la limite, on peut imaginer que chacun de mes objets métiers, qui contiendront les traitements, pourront hériter de, ou contenir, ce type d'objet... Comprenez-vous la problématique?
    Si je pose cette question, c'est que pour des problèmes de performance et de maintenance, les traitements de modification de données n'utiliseront pas les datastore (nous utiliserons plutot des requetes de modification "globales" UPDATE ... WHERE, plutot que le parcours des lignes 1 par 1 du datastore). Dès lors on peut se poser la question de l'utilité d'avoir un datastore différent pour chaque objet métier, et si utiliser un seul datastore "générique" n'est pas une bonne solution.


    Merci.


    mercredi 12 septembre 2007 06:47
  • Comme vous le dite le wrapper permet d'adapter une certaine "forme" en une autre... Mais cela sert aussi pour gérer les problèmes de ressources, d'allocation,...etc.

     

    Par exemple on peut dire que .NET est un "méga-wrapper", en effet celui-ci s'occupe de mettre tous les éléments de Windows en "objet", s'occupe de la gestion des Handle, ...etc.

     

    On peut appeler aussi cela un couche si vous le souhaitez... Bref l'essentiel est de comprendre le concept à adopter...

     

    J'ai regarder le DataStore, cela ressemble à un équivalent au DataTable de .NET...

     

    Je n'ai pas tellement bien compris votre 2ème paragraphe...

    En tout cas, si vous commencez à mettre du SQL dans l'IHM vous détruiser votre organisation...

     

    On va essayer de partir sur de bonnes bases :

    En fait, votre couche métier devrait contenir des classes du style :

    • C'est du C# (à la main), mais je suppose que c'est votre code PwB
    • On suppose que l'on gère des clients
    • Je considère que PwBxxxxx sont des types spéciquent à PwB

    class ClientDataManager

    {

           Constructeur();

           LibererRessources();

           PwBdecimal GetChiffreAffaireClient(int idClient);

           DataStore GetMauvaisClients(); 

           void DétruireClient(int idClient); 

    }

     

    J'espère que vous comprenez le principe de ces méthodes dans une couche métier...

    Cette classe est exposée, et visible sous l'IHM .NET...

     

    Pour créer une wrapper, vous devez faire quelque chose de ce style :

     

    class ClientDataManagerWrapper

    {

           ClientDataManager cdm;

     

           Constructeur()

           {

                  cdm = new ClientDataManager();

           }

     

           decimal GetChiffreAffaireClient(int idClient)

           {

                 PwBDecimal d;

                 d = cdm.GetChiffreAffaireClient(idClient);

     

                 return ConvertirDecimalPwB(d);

           }

     

           DataTable GetMauvaisClients()

           {

                  DataStore ds;

                  ds = cdm.ClientDataManager();

                 

                  return ConvertirDataStore(ds);

           }

     

           void DétruireClient()

           {

                 cdm.DétruireClient();

           }

     

           void Dispose()

           {

                cdm.LibérerRessources();

           }

    }

     

    Ce code peut vous paraître lourd mais présente les avantages suivants :

    • Vous typer tous vos objets en .NET et profitez des fonctionnalités de celui-ci ! (Par exemple pour le DataTable)
    • Vous gérez les ressources votre ClientDataManager dans cette classe et non directement dans l'IHM !
    • Vous avez un contrôle en amont des données qui arrive de la couche métier :

    Par exemple GetMauvaisClients() peut devenir :

     

    DataTable GetMauvaisClients()

     {

             DataStore ds;

             ds = cdm.ClientDataManager();

                 

             Pour toutes les lignes dans le DataStore

                   SupprimerClientSiChiffreAffaireTropPetit();

     

             return ConvertirDataStore(ds);

    }

    • Vous pouvez utiliser cette objet directement dans un ObjetDataSource...

    Est-ce que vous voyez ce que je veux dire par Wrapper ?

    Sachez que la méthode ConvertirDataStore() devrait être facile à réaliser car l'objet DataStore dispose de toutes les informations nécessaires (nom des colonnes, ...etc) pour être mis dans un DataTable.

     

    Vous pouvez aussi tirer profit des DataSet (et donc DataTable) typé...

    Il suffira d'écrire :

     

    ClientDataSet.ClientMauvaisDataTable GetMauvaisClients()

     {

             DataStore ds;

             ds = cdm.ClientDataManager();

                 

             Pour toutes les lignes dans le DataStore

                   SupprimerClientSiChiffreAffaireTropPetit();

     

             ClientDataSet.ClientMauvaisDataTable dt;

             dt = new ClientDataSet.ClientMauvaisDataTable();

     

             ConvertirDataStore(ds, dt);

     

             return dt;

    }

     

    J'espère que cela vous donne un bon petit aperçu...

     

    Si le DataManager ne représente pas trop votre concept que vous utilisez dans votre couche métier, pouvez-vous publier un exemple comme j'ai fait de vos objets/DataManager dans votre couche métier... ?

     

    N'hésitez pas et continuer à poser des questions....

     

    Cordialement

     

    mercredi 12 septembre 2007 13:01
    Modérateur
  • Ok!
    Merci pour votre aide, je pense comprendre le principe de wrapper. Ce n'est pas ce qu'on appelle un proxy?
    J'ai lu cet article: http://www.codeproject.com/csharp/SafeCOMWrapper.asp
    Cela me permet de changer la couche métier sans retoucher l'interface graphique, en retouchant uniquement ma bibliotheque de wrapper.

    Mais il y a un petit problème. Il m'est impossible:
    - de déclarer sous Visual Studio des types propres à PwB, du moins pas des datastore. A moins d'acheter le produit Datawindow.NET de Sybase, inutile à part pour çà. Il m'est aussi impossible de déclarer un type .NET sous PwB, mais on a vu que c'était pas correct conceptuellement.
    - de déployer sous forme d'assembly des objets dont des méthodes retournent un type complexe. Si une méthode de mon objet retourne un datastore, je ne pourrais pas déployer l'objet, à moins de ne pas sélectionner cette méthode pour le déploiement.

    Voici un petit exemple concret d'objet métier gérant des clients créé sous PwB:

    En vert, code PwB
    En bleu, code C#

    Je ne peux pas copier coller directement le script, tout se fait en visuel.

    Objet nvo_clients:
    Variable d'instance (attribut):

    datastore ids_client

    Constructeur:
    // Création de la transaction
    SQLCA = CREATE Transaction
    SQLCA.DBMS = "O90 Oracle9i (9.0.1)"
    SQLCA.ServerName = "xxx"
    SQLCA.logId = "xxx"
    SQLCA.logPass = "xxx"
    SQLCA.DBParm = "CommitOnDisconnect=No"
    CONNECT USING SQLCA;

    // Création de la liste de clients
    ids_client = CREATE datastore
    String ls_sql, ls_error
    ls_sql = "SELECT CD_CLIENT, ID_CLIENT, NOM, VILLE FROM CLIENT"
    ids_client.CREATE(SQLCA.SyntaxFromSQL (ls_sql, "", ls_Error))

    // Récupération des données
    ids_client.setTransObject(SQLCA)
    ids_client.retrieve()
    ids_client.setSort("CD_CLIENT A")
    ids_client.sort()

    Destructeur:
    DISCONNECT USING SQLCA;
    DESTROY SQLCA
    DESTROY ids_client

    Méthode long of_getNbClients()
    RETURN ids_client.rowCount()


    Méthode string of_getNom(long al_idClient)
    RETURN ids_client.getItemString(ids_client.find("CD_CLIENT = " + string(al_idClient), 0, ids_client.rowCount()), "NOM")

    Si je rajoute une méthode

    datastore of_getClients() { RETURN ids_clients }

    Je ne pourrais pas la sélectionner pour être "déployée", c'est à dire que je pourrais bien  transformer mon objet métier en DLL, mais il ne contiendra pas cette méthode...

    Etant donné que je ne peux pas directement utiliser de datastore en .NET, j'imagine que dans cet objet je devrais rajouter des méthodes:

    boolean of_supprimerDonnee(long i) {
    RETURN ids_clients.deleteRow(i);
    }


    string[] of_getColumn() {
    RETURN un_tableau_contenant_les_nom_des_colonnes }

    string of_getType(long i) {
    RETURN le_type_de_la_colonne_i // Le type "any" n'est pas déployable non plus... }

    int of_getChamp(long i, long j) {
    RETURN le_champ_de_la_colonne_j_à_la_ligne_i si c'est un type numérique }

    string of_getChamp(long i, long j) {
    RETURN le_champ_de_la_colonne_j_à_la_ligne_i si c'est un type chaine }

    datetime of_getChamp(long i, long j) {
    RETURN le_champ_de_la_colonne_j_à_la_ligne_i si c'est un type date }

    ...
    (pas certain que le polymorphisme basé sur uniquement le type de retour fonctionne, çà m'étonnerait beaucoup, je ne me suis encore jamais posé la question! sinon ce sera of_getChampNumber, ofGetChampString...)


    Ensuite, corrigez moi si je me trompe voici comment j'implémenterais mon wrapper en .NET:
    La syntaxe n'est pas bonne, je me concentre sur les principes :-)

    public class nvo_clientsWrapper {
    nvo_clients clients;

    nvo_clientsWrapper() {
    clients = new nvo_clients();

    // Pour en revenir à mon deuxième paragraphe de mon message précédent:
    // Ici se pose la question: puis-je mettre une requete SQL ici? Ceci afin d'utiliser un seul objet de type
    // nvo_recupDonnees identique à nvo_clients, mais dont le constructeur sera paramétré:
    // new nvo_recupDonnees("SELECT * FROM CLIENTS"), pour récupérer des clients,
    // new nvo_recupDonnees("SELECT * FROM FACTURES"), pour récupérer des factures...
    //
    // Mieux, peut-être que tous mes objets métiers (tel que nvo_clients) pourront hériter de cet objet:
    // new nvo_clients("SELECT * FROM CLIENTS")
    // new nvo_factures("SELECT * FROM FACTURES")...
    // Comme çà, le traitement de récupération des données (of_getType(), of_getChamp(), of_getColumn()... )
    // ne se fera qu'à un seul endroit, dans les méthodes du nvo_recupDonnees.
    // En gros nvo_recupDonnees consiste en une interface qui s'appellerait DonneesRecuperable...
    // Mais je suis conscient que je peux faire celà, tout en gardant ma requete dans mon constructeur, coté PwB
    // Je ne sais pas si je me fais conprendre!
    }

    boolean libererRessource() {
    return delete(clients); // ou une suppression d'enregistrements...
    }

    datatable getClients() {
    datatable dt = new datatable("clients");
    DataColumn column;
    DataRow row;

    // Génération des colonnes de la datatable
    String[] colonnes = clients.getColumn();
    for (int i=0; i < colonnes.Length; i++) {
    column = new Datacolumn:
    // Faire gaffe ici, sortir des type compréhensible...
    column.DataType = System.Type.GetType(clients.of_getType(i));
    column.ColumnName = colonnes[ i ];
    dt.Columns.Add(column);
    }

    // Génération des lignes
    Pour i=0 à clients.getNbClients() {
    - créer une nouvelle ligne DataRow.
    Pour j=0 à clients.getColumn().Length {
    - tester le type du champs
    - appeler la bonne méthode clients.of_getChamp()...
    - remplir le champs
    - passer au champs suivant
    }
    }

    return dt;
    }

    }


    Je suis sur la bonne voie? Mon code n'est pas très beau, mais c'est juste pour comprendre...

    Je suis obligé de "remplir" mon datatable. Mais je ne sais pas quelle est la meilleure méthode pour remplir ce datatable! Une approche telle que la mienne est-elle judicieuse (passer par un certain nombre de méthodes coté métier, et remplir le datatable "violemment" avec une double boucle imbriquée) ?? Ou existe-t-il un moyen plus intelligent, qui soit adapté à plusieurs milliers de lignes ?

    Et quelle est la différence entre datatable et dataset ??

    Dans tous les cas merci, j'espère ne pas trop vous embrouiller... Moi, j'y vois un peu plus clair, c'est déjà çà!
    mercredi 12 septembre 2007 14:24
  • Proxy, wrapper, couche.... vous chipotez sur les mots...

     

    S'il vous est impossible d'utiliser les DataStore sous .NET via COM, il vous faudra dans ce cas renvoyer des objets qui le soit ! (Une classe maison ?).

    Si vous devez fabriquer une classe, l'idéal est de faire comme vous l'avez fait, encapsuler votre DataStore et proposer des méthodes pour accéder aux données de celle ci....

    L'inconvénient d'un tel procédé c'est qu'il vous faudra passer du temps pour créer ces classes + le proxy...

    Vous pouvez aussi (pour faire générique) créer une classe MyDataStore qui encapsule un DataStore et qui propose les mêmes méthodes... Celle-ci devrait-être exploitable en .NET...

     

    Est-ce que vous pouvez créer et me compiler une petite DLL exploitable en .NET qui contient :

    2 classe avec des méthodes suivants dont l'une avec les méthodes suivantes :

    • int GetEntier(); 
    • string GetString(); 
    • int[] GetEntierTableau();
    • string[] GetEntierTableau();
    • UnObjet GetObjet();
    • UnObjet[] GetObjets();

    Et UnObjet est une autre classe crée par vous même dans la même DLL contenant n'importe quoi comme méthode.

    Envoyez la DLL (et le source) compilé à gilles.tourreau@pos.fr

    Je voudrais regarder le contenu comment cela peut-être exploité sous .NET en COM....

     

    Pour votre wrapper, cela est correct, mais tout dépend de ce que vous obtenez au niveau métier... Par contre le contructeur alors la je dis NON NON et NON !!!!!

    SQL est une technique pour accéder aux données des SGBD, et les SGBD sont un type de source de donnée...

    Ce n'est pas à la couche IHM (et métier d'ailleurs) de faire des requêtes SQL... C'est à la couche d'accès aux données !

     

    La couche IHM s'occupe de l'affichage des données et récupère celles-ci de la couche métier.

    La couche métier s'occupe de réaliser des "fonctions métiers" (Transformer une commande en facture par ex) et récupère les données de la couche d'accès aux données.

    La couche d'accès données s'occupe d'executer une requête SQL (pour un SGBD), ouvrir un fichier et initialiser un parseur XML (pour un fichier XML), se connecter un WebService,....etc... Elle se charge aussi de toute la gestion de la technique des sources de données (connexion réseau SGBD, Handle de fichier,...etc).

     

    La couche métier et l'IHM ne doivent avoir aucune notion "où sont les données physiquement et comment elles sont stockées"...

     

    Cordialement

     

     

     

     

     

    mercredi 12 septembre 2007 18:53
    Modérateur
  • Bonjour,
    Je vous envoie un mail contenant les sources et la DLL compilée. Je n'ai pas utilisé de datastore.

    En ce qui concerne la couche d'accès aux données, j'ai peut être tort, mais il me semble qu'elle doit consister en une interface "technique" entre ma couche métier et ma couche base de données. En d'autres termes, une couche d'accès aux données peut être, pour moi, quelque chose de transparent pour le développeur (liaison native Oracle, driver ODBC, connexion à un système de fichier, méthode de parsing XML...). On les utilise, mais on ne s'occupe pas de la façon dont çà marche derrière. La couche métier représentera l'architecture "conceptuelle" de mon système d'information (factures, clients, commandes...), ainsi que toutes les règles de gestion, et tous les traitements. Pour moi, des requetes de type SELECT * FROM CLIENT, ou UPDATE CLIENT WHERE font partie des traitements.

    Alors, évidemment, peut-être que je me gourre complètement, mais si chaque requete doit se trouver dans un autre objet intermédiaire aux couches métier / BdD, je vais me retrouver avec N objets métiers (un objet pour les clients, les factures, les commandes...) et N objets pour la couche d'accès aux données (un pour gérer la récupération des clients, un autre pour la récupération des factures...).

    D'autant plus que le langage SQL est "standardisé". Oui, bon, du moins en théorie . Donc si un jour je dois changer ma couche BdD, toutes mes requêtes doivent fonctionner, non? Au pire, je peux déporter ma déclaration de transaction (spécifique au moteur) dans un objet à part...
    Après, évidemment, couche de données ne signifie pas forcément base de donnée SQL! Mais si un jour nous devons migrer notre base Oracle vers une couche basée sur des fichiers, des web services ou des document XML, alors cela signifie que nous aurons pris un tournant complètement à l'opposé de nos problématiques d'aujourd'hui .

    Je suis par contre d'accord avec vous sur le fait que la requête SQL ne doit pas apparaitre dans l'IHM. J'ai fait remonter une idée de mes collègues, qui pensent qu'il serait interessant de n'utiliser qu'un seul type d'objet (nommé par exemple RECUP) propre à la récupération de données. Les objets CLIENT, FACTURE... ne contiendraient alors que des traitements. Mais avec un héritage, cela regle le problème je pense. Chaque objet CLIENT, FACTURE... hériteront de l'objet RECUP pour que les données des CLIENTS, FACTURES,... puissent être affichées. La requete SQL peux toujours se trouver dans l'objet CLIENT, FACTURE...

    En ce qui concerne le wrapper, j'imagine qu'il n'y a pas de meilleur moyen qu'une double boucle pour remplir un datatable... Par contre, quelle est la différence entre les différent types de collection (datareader, datatable, dataset) ??

    Merci encore.
    jeudi 13 septembre 2007 08:04
  • Bonjour,

     

    J'ai bien reçu votre DLL... En fait ce n'est pas du COM que vous faites ! Vous accédez directement à une DLL qui est assembly .NET qui est généré à partir de PwB... Cela change pas mal de chose...

    Il sera peut-être inutile de générer un wrapper (coté .NET), vos données sont déjà des objets .NET... Vous pouvez donc utiliser ces objets directement dans votre IHM...

     

    En ce qui concerne la couche d'accès aux données et métier, mettre du SQL dans une couche métier est une erreur.

    Car vous rendez votre couche métier dépendant d'un type de source de données !

    Normalement dans une application 3-tiers vous devez avoir "3 DLL" (ou bibliothèque pour le terme générique) :

     

    IHM.DLL

    Métier.DLL

    AccèsDonnées.DLL

     

    Imaginons une application tournant sur PHP sur une BD Oracle, on retrouve ce genre de DLL :

     

    PHP.DLL

    Métier.DLL

    Oracle.DLL

     

    Dans une bonne application architecturée, vous devez être capable de proposer à vos clients d'utiliser cette application dans un autre IHM, sans recompiler Métier.DLL et Oracle.DLL par exemple ASP.DLL :

     

    ASP.DLL

    Métier.DLL

    Oracle.DLL

     

    Il en est de même avec l'accès aux données ! Vous devez être capable de proposer un autre type d'accès aux données (par exemple XML.DLL) sans recompiler l'IHM et Métier.DLL :

     

    ASP.DLL ou PHP.DLL

    Métier.DLL

    XML.DLL

     

    Dans votre cas, en incluant du code SQL dans votre couche métier, changer le type de source de données, entraine une modification et recompilation de votre couche métier !

    En résumé, votre couche métier doit être la seule bibliothèque qui ne doit jamais être changée quelque soit l'IHM ou l'accès aux données choisie...

     

    Après il existe bien évidemment des exceptions... Par exemple (et cela pour cause de performance), des applications possèdent leur couche métier et données ancré dans un SGBD via des procédures stockées, trigger, vues....etc.

    Certes cette solution offre de très bonnes performances, mais oblige le métier de votre application à être dépendant des SGBD (et voir même d'un SGBD particulier car le langage des procédures stockées par exemple, n'est pas tellement normalisé...). Il est (et sera) donc très difficile de changer de type d'accès aux données dans ce cas...

     

    Vous pouvez aussi dans certains cas avoir une couche d'accès aux données générique pour les SGBD uniquement (C'est le cas pour moi), mais le principe reste le même on se retrouve avec ce genre de DLL :

     

    IHM.DLL

    Métier.DLL

    SGBD.DLL

     

    Et dans SGBD.DLL, on trouve du code SQL normalisé... Mais cela reste une très bonne architecture, car on peut changer de type de source de données (autre que les SGBD)... XML, WebService,...etc.

     

    C'est comme cela une bonne organisation d'une application 3-tiers.

    Après il existe des variantes ou l'on coupe la couche métier en deux:

    - les objets (DataTable)...

    - les traitements (GetClients())...

    Mais cela reste toujours dans la couche métier !

     

    En ce qui concerne le problème de "Transaction", il est vrai qu'il est difficile à mettre en oeuvre dans une architecture 3-tiers, car c'est un traitement du SI qui est transactionnel, mais en fait cela ce gère au niveux de l'accès aux données...

    Il faut dans ce cas créer un sytème de transaction générique au niveau métier et dont la couche d'accès aux données devra si la couche métier la ordonnée créer, committer ou rollbacker un transaction...

    Microsoft a d'ailleurs mis au point une petite merveille dans l'espace de nom System.Transactions... Cette espace de nom permet de gérer des transaction indépendemment de la technologie utilisée...

     

    ******************

     

    Pour revenir à votre DLL, étant donné qu'il vous est impossible d'exposer un DataStore, vous devriez donc coder en PwB vos n objets métiers comme vous l'avez proposez...

    Votre DLL que vous m'avez fourni, montre qu'il est possible d'utiliser des tableaux d'objets .NET ! Et les tableaux sous .NET implémente l'interface IEnumerable... Ce qui veut dire qu'il ne vous sera pas nécessaire de créer des DataTable ou autre chose. Vous pouvez donc directement mettre dans un BindingSource ou ObjectDataSource un tableau de vos objets métier !

    Il sera donc pas nécessaire de créer un wrapper et d'utiliser les DataTable comme je l'avais supposé...

     

    En ce qui concerne la classe de base RECUP, la je ne peux pas vous en dire en plus... Tout dépend de votre organisation métier qui est déjà codé en PwB, des possibilités d'héritage de PwB, de ce que vous souhaitez factoriser,....

     

    Pouvez vous m'en dire un peu plus sur les classes déjà codé de votre application métier ? Comment sont-elle organisé ? Comment sont elle instancié ? Appellée ?

     

    Cordialement

     

    jeudi 13 septembre 2007 10:29
    Modérateur
  • Ok,
    Alors il faut savoir que nous n'avons pas à l'heure actuelle d'architecture objet très "académique". Notre base de données est parfaitement constituée, mais la couche objet n'est pas forcément en relation directe avec ce qu'il y a dans la base.
    Je m'explique.
    Prenons l'exemple de l'objet inventaire. Cet objet est, si l'on regarde la base, composé de lignes d'inventaire, et chacune de ces lignes est composée de détails.
    3 tables donc :
    - inventaire: clés (cd_inventaire),
    - inventaire_lg: clés (cd_inventaire, n_ligne),
    - inventaire_lg_det: clés (cd_inventaire, n_ligne, n_det).

    Mais pas 3 objets.
    Un seul objet nvo_inventaire. Cet objet est composé de l'objet nvo_cursor, héritant lui-même de l'objet datastore. Le nvo_cursor permet de construire dynamiquement une collection PwB à partir d'une requête SQL. C'est cet objet qui nous permettra de parcourir les données quand il y a besoin de les parcourir.

    Le constructeur de cet objet initialise un certain nombre de variables d'instances (attributs en terme objet). Mais il est impossible en PwB de faire des constructeurs paramétrés. Nous avons donc plusieurs méthodes que l'on appelle of_init(). Chacune de ces méthodes of_init() va initialiser l'objet différemment en fonction du besoin:
    - of_init() : initialise l'objet pour un traitement sur tous les inventaires. le nvo_cursor sera chargé avec une requete de type SELECT * FROM inventaire.
    - of_init(al_cd_inventaire) : initialise l'objet pour un traitement sur un inventaire particulier.
    - of_init(al_cd_inventaire, al_n_ligne) : initialise l'objet pour un traitement sur une ligne d'un inventaire particulier.
    - of_init(al_cd_inventaire, al_n_ligne, al_n_det) : initialise l'objet pour un traitement sur un détail d'une ligne d'un inventaire particulier.

    Même principe pour les traitement. Si j'appelle ma méthode of_delete(), une requete de type DELETE FROM inventaire sera executée différemment en fonction de la méthode of_init() qui a été appelée préalablement. Pour les traitements comme ceci (mise à jour, suppression...) on utilise directement des requetes dans le code PwB.

    On peut faire ce genre de choses:
    long il_cd_inventaire
    il_cd_inventaire = 4
    DELETE FROM inventaire WHERE cd_inventaire = :il_cd_inventaire USING SQLCA;

    Pour supprimer une détail, nous n'appelons pas la méthode of_delete de l'objet inventaire, qui appelle la méthode of_delete de l'objet inventaire_ligne, qui appelle la méthode of_delete de l'objet inventaire_ligne_detail.

    Notre couche métier actuelle est donc très fortement couplée à la couche base de données, et nous avons une approche très "fonctionnelle". Nous crééons nos objets pour réaliser un traitement (parfois même certains de nos objets possèdent le nom du traitement qu'il doit accomplir, au lieu du nom du type d'information qu'il doit traiter), pas pour coller à une architecture. Nous nous basons SUR la base de données, qui elle est tout ce qu'il y a de plus stable et cohérente.

    C'est très performant, fonctionnel, et rapide dans le développement, mais nous sommes conscient que cela peut paraitre incohérent.

    Voilà voilà. Je ne sais pas si c'est ce que vous souhaitiez savoir.

    *****************************

    On peut effectivement retourner un tableau d'objet. Mais il faut quand même le remplir, ce tableau!! Or, nous récupérons nos données dans un datastore. Même si la technologie COM est "encapsulée" dans des assemblies, nous sommes toujours limités à l'utilisation de types simples, et donc de méthodes pour adapter ces types...
    Remplir une collection à partir d'une autre collection, c'est un peu adapter un type pour l'utiliser dans une autre couche non? C'est donc le principe d'un wrapper... Alors, quit à développer une méthode qui remplit une collection compréhensible par .NET à partir d'un datastore, n'est-il pas préférable de le faire côté .NET? Il me semblait que la couche métier se devait de ne pas adapter son format à l'IHM... 

    En plus, la gestion des datatable n'est elle plus poussée que celle des tableaux (notion de cache...)??

    Au passage, la connection d'un objectdatasource avec un tableau d'objets fonctionne? Comment est il capable de créer les noms des colonnes? Automatiquement avec les noms des attributs des objets? Et si mes attributs sont déclarés "private" ?? Il faut faire un binding entre des méthodes getter et setter et des champs ??? Je comprenais avec les datatables, qui sont composées d'objets "DataColumn", et les noms des colonnes était accessible. Mais j'ai du mal à voir comment cela peut fonctionner avec un tableau d'objets... Mais l'approche est envisageable, oui.

    Enfin, dernière question qui n'a rien a voir. Organisez-vous des formations (vous ou votre entreprise)? Cela nous permettrai d'aborder toutes nos questions de vive voix, et de faire participer mon service.
    jeudi 13 septembre 2007 13:04
  • Ok,

     

    Je vous mieux maintenant votre organisation...

    Juste une question, comment faites vous pour récupérer (par exemple) la désignation, le prix et la quantité d'une ligne de votre objet inventaire ? Pouvez-vous montrer un code succint en PwB ?

     

    Maintenant ce que je ne comprends plus c'est en ce qui concerne le code PwB... Quand vous le compilez, que produit-il en sortie ?

    Un executable/DLL standard Windows ou un Assembly .NET ?

    Par DLL standard Windows, j'appelle une DLL classique natif que l'on compile en C++ par exemple... On ne peux pas les exploiter directement en .NET, il faut passer par le mécanisme de P/Invoke (comme si on voulais utiliser les API Windows).

     

    Car ce que vous m'avez passé ce matin, c'est :

    • du code PwB.... Ok
    • une DLL généré par ce code... Ok

    Or, je constate que cette DLL n'est pas une DLL Windows classique ! C'est un assembly .NET...

    Et apparement quand je regarde les références nécessaire pour cette assembly il faut :

    • Sybase.PowerBuilder.Common
    • Sybase.PowerBuilder.Core
    • ....
    • Et même System.Windows.Forms

    Et ces DLL de Sybase, sont elles aussi des assembly...

     

    Donc je voudrais savoir :

    Quand vous compilez un code PwB, produisez-vous des assembly .NET ou des DLL/Executable Windows classique ?

     

    ******************************

    Il faut savoir que le DataBinding de .NET, peut utiliser des objets pour les afficher au lieu du DataTable... Il faut pour cela utiliser une collection IEnumerable (tableaux, collection, liste, dictionnaire,...etc) et un objet qui possède des propriétés...

    Par exemple en C# :

     

    public class Client

    {

           private string nom; 

           private int age;       

     

           public Client()

           {

                 this.nom = nom;

                 this.age = age;

            }

     

           public string Nom

           {

                get { return this.nom; }

                set { this.nom = value; }

           }

           public int Age

           {

                get { return this.age; }

                set { this.age= value; }

           }

    }

     

    Vous faites un tableau de client :

     

    Client[] clients = new Client[2];

    clients[0] = new Client("DUPOND", 16);

    clients[1] = new Client("DURAND", 64);

     

    Dans un BindingSource ou ObjectDataSource vous liez ce tableau, le DataBinding pourra automatiquement liée les propriété Nom et Age à des contrôles... Et cela fonctionne avec tout type d'objet ! Même ceux qui ne représente pas des données. (Form, AppDomain, bref n'importe quel type de classe !).

     

    ******************************

    En ce qui concerne votre dernière question je vous répondrais par mail PV.

     

    Cordialement

     

     

    jeudi 13 septembre 2007 18:35
    Modérateur
  • Bonjour,

    Si nous souhaitons seulement récupérer le contenu du champ, nous utilisons une méthode of_getRecord() de l'objet nvo_cursor, qui retourne un type ANY (type générique PwB).

    Si nous souhaitons afficher des données, nous utilisons des Datawindow. C'est objet visuel propre à PwB, qui se construit à partir d'une requête SQL. Elle se décline en plusieurs types (grilles, détails...). Tous nos affichages sont créés comme çà, on créé la datawindow à partir d'une requête, puis on pose cette datawindow sur la fenêtre en "drag & drop", comme si je voulais poser un bouton... En gros.
    ___________________________________________________

    Ensuite, en ce qui concerne les DLL, ce sont bien des assemblies .NET qui sont générées. Elle s'intègrent directement dans l'environnement .NET. Par contre, si effectivement le référencement de cette DLL necessite d'autres DLL ( Sybase.PowerBuilder.Common, Code, etc...), j'en ai encore jamais pris conscience. Etant donné que j'ai installé PwB, je dois avoir le répertoire contenant ces DLL correctement renseigné dans le PATH... Je ne me suis pas encore préoccupé de çà. Ce sont des problématiques d'install, et nous sommes encore très loin de livrer notre nouveau produit
    En tous cas, je vous confirme que ce sont bien des assemblies.
    ___________________________________________________

    Votre idée du wrapper nous semblait en fait une bonne voie à suivre. Même si je peux retourner des tableaux d'objets côté PwB et les intégrer dans VS, cela nous apporte 2 problèmes:
    - Nous devons construire remplir ce tableau à partir du datastore, et côté PwB. C'est donc implémenter une sorte de wrapper côté PwB, et c'est un peu moche...
    - Nous devrons construire tous les attibuts, et toutes les méthodes d'accessibilité get/set pour chacun de nos objets métiers. Alors oui, c'est ce que nous DEVRIONS faire, mais nous souhaitons avoir quelquechose de plus simple, et c'est en partie pourquoi nous couplons si fortement notre couche métier avec notre couche base de données. La représentation de l'information côté métier se fait en fonction du datastore et de la requête qui le construit, plutot que par les attributs qui le composent. Inutile de vous dire aussi que nous avons des centaines de tables, dont certaines possèdent plus de 20 champs... Et aucun moyen en PwB pour générer les constructeur, getter et setter automatiquement (<> Eclipse). Il faut mieux que nous trouvions quelquechose de plus générique!
    vendredi 14 septembre 2007 06:52
  • Maintenant une petite question avant de continuer :

     

    Votre couche métier en PwB compilé est-elle une assembly .NET ? ou une DLL classique ?

     

    Cordialement

    vendredi 14 septembre 2007 10:50
    Modérateur
  • Euh... J'ai répondu dans le message précédent, non?

    Ma couche métier PB de la nouvelle architecture sera une assembly (ou plusieurs assemblies) .NET.
    Ma couche métier actuelle, celle de notre produit aujourd'hui, n'est pas une DLL. C'est une architecture client/serveur. Tout est dans le même client, tout dans le même langage. Le but est donc de prendre chacun des objets métier PB actuels, et les déployer sous forme d'assembly.

    Je ne suis pas sûr de comprendre... Ce qui est certain, lorsque je déploie mon objet métier PB, c'est une assembly .NET qui est générée. Alors, effectivement, cette assembly est représentée par une DLL, mais elle est directement exploitable dans Visual Studio. Je ne vois pas quoi dire de plus!
    vendredi 14 septembre 2007 11:22
  • Ok, je continu notre discussion par e-mail avec de beau schéma...

    vendredi 14 septembre 2007 18:37
    Modérateur