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

QxOrm Discussion :

Relation et héritage


Sujet :

QxOrm

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut Relation et héritage
    Bonjour à nouveau,

    J'ai une dernière question:

    J'ai une classe Property et une spécialisation de celle-ci en PropertyString, ensuite, j'ai une autre classe Component avec relation OneToMany sur des propriétés, donc une liste de pointeurs sur Property qui peuvent être des instances de PropertyString.

    Lorsque j'insère une instance comp de Component avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    qx::dao::insert( comp, QStringList() << "*" );
    comment faire pour que ce soit le type PropertyString qui soit enregistré dans la base de donnée au lieu de Property ?

    Même en spécifiant le lien de parenté avec la macro prévue à cet effet, je ne parviens pas à le faire.

    J'ai lu tous les tutoriaux et doc, mais je ne trouve pas ce qu'il me faut...

  2. #2
    Expert confirmé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    481
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 481
    Points : 4 238
    Points
    4 238
    Par défaut


    C'est une problématique assez complexe, et je ne sais pas si il y a de "bonnes" solutions.
    Je serai curieux de savoir comment Hibernate arrive à gérer ce genre de situation : peut-être que ce serait intéressant que tu poses la question sur un forum Hibernate pour voir comment ils font...

    Dans tous les cas, il ne faut pas oublier qu'il y a une base de données derrière ton programme C++, et qu'à chaque classe C++ est associée une table de la BDD. Donc concrètement, ça signifie qu'il faut autant de relations one-to-many que de types possibles dans ta collection.

    Donc si je reprends ton exemple, voici une solution que je te propose : tu as une classe de base Property, et admettons que tu as les 3 classes dérivées suivantes : PropertyString, PropertyDouble et PropertyBool.
    Maintenant, admettons que ta classe Component possède une liste de Property (cette liste pouvant contenir des instances de PropertyString, PropertyDouble et PropertyBool).
    La solution que je te propose consiste à avoir 3 collections différentes dans ta classe Component (donc 3 relations one-to-many à définir), MAIS de faire en sorte que vu de l'extérieur de la classe, on ne voit qu'une seule collection (donc en gros, la complexité se situe à l'intérieur de la classe, pas pour celui qui va utiliser ta classe).

    Voici du code, ce sera peut-être plus parlant (attention, j'écris ça from-scratch, il y a peut-être des erreurs de syntaxe) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Component
    {
     
    private:
     
       qx::QxCollection<QString, boost::shared_ptr<PropertyString> > m_lstPropString;
       qx::QxCollection<QString, boost::shared_ptr<PropertyDouble> > m_lstPropDouble;
       qx::QxCollection<QString, boost::shared_ptr<PropertyBool> > m_lstPropBool;
     
    public:
     
       // Ici on propose des méthodes comme s'il n'y avait qu'une seule collection
       long getPropCount() const;
       boost::shared_ptr<Property> getPropByIndex(long lIndex) const;
       boost::shared_ptr<Property> getPropByKey(const QString & sKey) const;
       bool removeProp(const QString & sKey);
       void removeAllProp();
       bool addProp(boost::shared_ptr<Property> pProperty);
       // etc...
     
    };
    Et dans ton implémentation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    namespace qx {
    template <> void register_class(QxClass<Component> & t)
    {
       // ...
       t.relationOneToMany(& Component::m_lstPropString);
       t.relationOneToMany(& Component::m_lstPropDouble);
       t.relationOneToMany(& Component::m_lstPropBool);
       // etc...
    }} // namespace qx
     
    long Component::getPropCount() const
    {
       return (m_lstPropString.count() + m_lstPropDouble.count() + m_lstPropBool.count());
    }
     
    boost::shared_ptr<Property> Component::getPropByKey(const QString & sKey) const
    {
       if (m_lstPropString.exist(sKey)) { return m_lstPropString.getByKey(sKey); }
       else if (m_lstPropDouble.exist(sKey)) { return m_lstPropDouble.getByKey(sKey); }
       else if (m_lstPropBool.exist(sKey)) { return m_lstPropBool.getByKey(sKey); }
       return boost::shared_ptr<Property>();
    }
     
    bool Component::addProp(boost::shared_ptr<Property> pProperty)
    {
       // on utilise dynamic_cast<> pour savoir où ajouter l'élément
       Property * pPropertyPtr = pProperty.get();
       if (dynamic_cast<PropertyString *>(pPropertyPtr)) { m_lstPropString.insert(pProperty->getKey(), pProperty); }
       else if (dynamic_cast<PropertyDouble *>(pPropertyPtr)) { m_lstPropDouble.insert(pProperty->getKey(), pProperty); }
       else if (dynamic_cast<PropertyBool *>(pPropertyPtr)) { m_lstPropBool.insert(pProperty->getKey(), pProperty); }
       else { qAssert(false); /* type non géré */ }
    }

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    Un grand merci pour cette réponse très complète !

    Je vais analyser ça après avoir mangé ! Merci encore !

  4. #4
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    Ok, je vois !

    C'est assez gênant, car du coup, on perd l’intérêt de l'héritage...
    Je vais faire comme tu as dit, mais toutefois, j'ai une idée qui pourrait résoudre le pb, mais il faut faire pas mal de modifs dans QxOrm.

    Il faudrait hériter d'un objet QxObject au lieu de QObject, dont la classe contiendrait une méthode virtuelle tel que:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual void insert() = 0;
    Ensuite, chaque object héritant de QxObject devrait insérer la macro QX_OBJECT, laquelle ne ferait qu'ajouter cette méthode:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    virtual void insert()
    { qx::dao::insert( this ); }
    Ensuite, il faudrait que le moteur appelle la méthode insert de la classe si celle ci hérite de QxObject. Ainsi, on serait certain du type à l'insertion

    Qu'en penses-tu ?

  5. #5
    Expert confirmé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    481
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 481
    Points : 4 238
    Points
    4 238
    Par défaut
    C'est assez gênant, car du coup, on perd l’intérêt de l'héritage...
    Oui, c'est vrai, mais il ne faut pas oublier que derrière chaque classe C++ que tu définis, il y a une table associée dans la base de données.

    Il faudrait hériter d'un objet QxObject au lieu de QObject, dont la classe contiendrait une méthode virtuelle
    Oui, c'est un peu ce que j'essaye de mettre en place dans la prochaine de version de la bibliothèque (QxOrm 1.2.3) : j'ai créé une nouvelle interface (ou plutôt classe abstraite en C++ ) nommée qx::IxPersistable.
    Tu peux télécharger une version BETA avec cette interface disponible si tu veux la tester :
    http://www.qxorm.com/version/QxOrm_1.2.3_BETA_05.zip

    Voici ce que propose l'interface qx::IxPersistable :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /*!
    * \ingroup QxDao
    * \brief qx::IxPersistable : common interface (abstract class) for persistents classes using QX_PERSISTABLE_HPP() and QX_PERSISTABLE_CPP() macros
    *
    * To use this common interface for persistents classes :
    * <b>1-</b> inherit your persistent class from <i>qx::IxPersistable</i> ;
    * <b>2-</b> into your class definition (<i>myClass.h</i> for example), add <i>QX_PERSISTABLE_HPP(myClass)</i> macro ;
    * <b>3-</b> into your class implementation (<i>myClass.cpp</i> for example), add <i>QX_PERSISTABLE_CPP(myClass)</i> macro.
    *
    * <b>Note :</b> for a list of objects (<i>qxFetchAll()</i> method or <i>qxFetchByQuery()</i> method), use <i>qx::QxCollection<type_primary_key, boost::shared_ptr<my_type>></i> type.
    * Or just use <i>qx::IxPersistableCollection<T>::type</i> to retrieve your persistent collection type.
    */
    class IxPersistable
    {
    public:
       virtual long qxCount(const qx::QxSqlQuery & query = qx::QxSqlQuery(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxFetchById(const QVariant & id = QVariant(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxFetchAll(qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxFetchByQuery(const qx::QxSqlQuery & query, qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxInsert(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxUpdate(const qx::QxSqlQuery & query = qx::QxSqlQuery(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxSave(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxDeleteById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxDeleteAll(QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxDeleteByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxDestroyById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxDestroyAll(QSqlDatabase * pDatabase = NULL) = 0;
       virtual QSqlError qxDestroyByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL) = 0;
       virtual qx_bool qxExist(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL) = 0;
       virtual qx::QxInvalidValueX qxValidate(const QStringList & groups = QStringList()) = 0;
       virtual boost::shared_ptr<qx::IxCollection> qxNewPersistableCollection() const = 0;
    };
    Donc, en gros pour l'utiliser, il faut :
    1- ta classe de base doit hériter du type qx::IxPersistable
    2- tu dois utiliser les macros QX_PERSISTABLE_HPP et QX_PERSISTABLE_CPP sur chacune de tes classes dérivées
    3- ensuite, tu pourras écrire quelque chose comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    foreach(Property * p, lst)
    { daoError = p->qxInsert(); }
    Cependant, je ne suis pas sur que ce soit la meilleure des solutions pour ton cas.
    Il faut que tu testes et que tu décides ce qui est le mieux pour ton contexte d'utilisation...

  6. #6
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    Ok, je vois, c'est presque parfait !

    Mais le gros problème surviens avec les relations:
    Je n'ai pas le moyen de faire l'insert automatique des bonnes propriétés à l'insertion de mon composant.
    en gros, cette ligne va m'inserer des Property plutôt que le type spécifique:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    qx::dao::insert( composant, QStringList() << "*" );
    non ?

    (Je vais tester pour voir si je peux insérer les propriétés côté objet Property).

  7. #7
    Expert confirmé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    481
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 481
    Points : 4 238
    Points
    4 238
    Par défaut
    Mais le gros problème surviens avec les relations:
    Je n'ai pas le moyen de faire l'insert automatique des bonnes propriétés à l'insertion de mon composant.
    Si tu utilises l'interface qx::IxPersistable, peut-être qu'il ne faut pas que tu définisses t.relationOneToMany(...) dans ton mapping.
    Par contre, à la place tu pourrais par exemple utiliser les trigger sur ta classe Component :
    * au fetch_after => je fetch toutes les property en intérogeant chaque table
    * au insert_after => j'insère toutes les property en utilisant "p->qxInsert()"
    * au update_after => j'insère toutes les property en utilisant "p->qxUpdate()"
    * au remove_after => je supprime toutes les property associées au component.

    En utilisant les trigger, ça reste complètement transparent pour celui qui va utiliser ta classe.
    Pour plus de détails sur les trigger : http://qt.developpez.com/faq/?page=b...#qxorm-trigger
    C'est juste une idée comme ça, à tester, y a que toi qui pourra décider ce qui est le mieux pour ton projet...

  8. #8
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    Ah ok, je vois ! Très bonne idée ! Bien joué

    Ca, ça me plais ! Merci beaucoup !

  9. #9
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    C'est bon ! Ton idée fonctionne à merveille ! C'est fabuleux !!!!!

  10. #10
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    J'ai juste quelques soucis avec mon delete:
    Je vois bien quelque chose dans le genre, mais je n'en suis pas certain:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void FormComponent::onBeforeDelete( qx::dao::detail::IxDao_Helper * dao )
    {
       Q_UNUSED( dao );
       Q_FOREACH( const FormComponentProperty::Ptr & p, properties )
       {
           p->qxDeleteById( QVariant( p->id ) );
       }
       properties.clear();
    }

  11. #11
    Expert confirmé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    481
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 481
    Points : 4 238
    Points
    4 238
    Par défaut
    Q_UNUSED( dao );
    Dans ton cas, il sert à quelque chose celui-là, ne le met pas UNUSED
    Il permet de récupérer la connexion courante afin de l'utiliser dans ton trigger (pour être certain d'utiliser la même connexion à la BDD).
    Ça présente 2 gros avantages :
    1- optimisation car tu utilises la même connexion à la BDD ;
    2- si tu as ouvert une transaction (ce que je te conseille fortement de faire dans ton cas, en utilisant qx::QxSession par exemple), tu restes dans le contexte de la transaction pour éventuellement faire un ROLLBACK si nécessaire (et ainsi de ne pas avoir une BDD bancale avec des données à moitié enregistrées ou à moitié supprimées).

    Autre chose, je te conseille plutôt de supprimer en utilisant une requête plutôt que de parcourir la liste.

    Donc au final, le code que j'écrirais ressemblerait plus à ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void FormComponent::onBeforeDelete( qx::dao::detail::IxDao_Helper * dao )
    {
       QSqlDatabase & db = dao->database();
       QSqlError daoError = qx::dao::delete_by_query<PropertyString>(qx_query().where("ComponentId").isEqualTo(this->getId()), & db);
       daoError = qx::dao::delete_by_query<PropertyDouble>(qx_query().where("ComponentId").isEqualTo(this->getId()), & db);
       daoError = qx::dao::delete_by_query<PropertyBool>(qx_query().where("ComponentId").isEqualTo(this->getId()), & db);
       properties.clear();
    }

  12. #12
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    Ah oui ! Effectivement, tu fais bien de me souligner ce point ! C'est crucial vu que je fait des sessions.

    Ok, j'ai compris pour le delete. Merci !

  13. #13
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    Citation Envoyé par QxOrm Voir le message
    * au fetch_after => je fetch toutes les property en intérogeant chaque table
    Je ne parviens pas à trouver la méthode onAfterFetch dans la structure QxDao_Trigger, où se trouve-t-elle ? Quelle est la démarche à suivre pour mettre un trigger sur le fetch ?

  14. #14
    Expert confirmé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    481
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 481
    Points : 4 238
    Points
    4 238
    Par défaut
    Je ne parviens pas à trouver la méthode onAfterFetch dans la structure QxDao_Trigger
    C'est vrai, ça n'existe pas
    Quand j'ai mis en place les triggers, je pensais que ça n'avait aucune utilité.
    Et en relisant ce sujet, il y a une utilité pour ton cas : je vais donc l'ajouter dans la prochaine version...
    Je te ferai une BETA et l'ajouterai à ce sujet pour que tu puisses profiter de la fonctionnalité avant que la prochaine version ne sorte officiellement...

  15. #15
    Expert confirmé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    481
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 481
    Points : 4 238
    Points
    4 238
    Par défaut
    Je te ferai une BETA et l'ajouterai à ce sujet pour que tu puisses profiter de la fonctionnalité avant que la prochaine version ne sorte officiellement...
    Voici une BETA avec les triggers sur le fetch (même fonctionnement que pour les autres triggers...) :
    http://www.qxorm.com/version/QxOrm_1.2.3_BETA_08.zip

  16. #16
    Membre du Club
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    151
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 151
    Points : 49
    Points
    49
    Par défaut
    Citation Envoyé par QxOrm Voir le message
    Voici une BETA avec les triggers sur le fetch (même fonctionnement que pour les autres triggers...) :
    http://www.qxorm.com/version/QxOrm_1.2.3_BETA_08.zip
    Ok super ! Merci

    Au passage, je pense que tu devrais passer à un repository GitHub, c'est très pratique !
    Par exemple, tu pourrais créer des branches pour les béta.

    Personnellement, je me suis installé un serveur gitorious en local pour mes expérimentations.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. mapper une relation d'héritage
    Par DoubleU dans le forum Hibernate
    Réponses: 3
    Dernier message: 29/11/2008, 02h19
  2. [MCD] Comment créer une relation d'héritage dans Access
    Par Marounda dans le forum Schéma
    Réponses: 4
    Dernier message: 11/01/2008, 16h28
  3. relation d'héritage entre acteurs
    Par pigeon11 dans le forum Cas d'utilisation
    Réponses: 8
    Dernier message: 31/08/2007, 18h34
  4. Relations d'héritage dans un SGBD
    Par mawi dans le forum Access
    Réponses: 3
    Dernier message: 18/04/2005, 15h15
  5. Relations d'héritage dans un SGBD
    Par mawi dans le forum Décisions SGBD
    Réponses: 2
    Dernier message: 13/04/2005, 23h51

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