QxOrm Windows Linux Macintosh C++

Home Download Quick sample Tutorial Faq Link

QxOrm >> Faq Version courante : QxOrm 1.1.9 (LGPL) - documentation en ligne de la bibliothèque QxOrm Version française du site Web site english version
qt_ambassador
QxOrm library has been accepted into the Qt Ambassador Program

Qu'est-ce que QxOrm ?

QxOrm est une bibliothèque C++ open source de gestion de données (Object Relational Mapping, ORM) sous licence LGPL.
QxOrm est développé par Lionel Marty, Ingénieur en développement logiciel depuis 2003.

À partir d'une simple fonction de paramétrage (que l'on peut comparer avec un fichier de mapping XML Hibernate), vous aurez accès aux fonctionnalités suivantes :
  • persistance : communication avec de nombreuses bases de données (avec support des relations 1-1, 1-n, n-1 et n-n) ;
  • sérialisation des données (flux binaire et XML) ;
  • moteur de réflexion (ou introspection) pour accéder aux classes, attributs et invoquer des méthodes.
QxOrm est dépendant des excellentes bibliothèques boost (compatible à partir de la version 1.38) et Qt (compatible à partir de la version 4.5.0).
La bibliothèque QxOrm a été retenue pour faire partie du programme Qt Ambassador.


Comment contacter QxOrm pour indiquer un bug ou poser une question ?

Si vous trouvez un bug ou si vous avez une question concernant le fonctionnement de la bibliothèque QxOrm, vous pouvez envoyer un mail à : support@qxorm.com.
QxOrm est également disponible sur le site SourceForge : plate-forme d'hébergement de projets de développements de logiciels libres.
Un forum (en anglais) dédié à QxOrm est disponible en cliquant ici.
Vous pouvez également retrouver la communauté française de QxOrm sur le forum de Developpez.com.


Comment compiler QxOrm ?

QxOrm utilise le processus qmake de la bibliothèque Qt pour générer les makefile et compiler le projet.
qmake est multiplateforme et fonctionne parfaitement sous Windows, Linux (Unix) et Mac.
Pour compiler QxOrm, il suffit d'exécuter les commandes suivantes :

qmake
make debug
make release

Sous Windows, des fichiers *.vcproj et *.sln sont disponibles pour les éditeurs Visual C++ 2008 et Visual C++ 2010.
Les fichiers *.pro sont lisibles par l'éditeur Qt Creator, et des plugins existent permettant de s'interfacer avec de nombreux éditeurs C++.
Les fichiers mingw_build_all_debug.bat et mingw_build_all_release.bat présents dans le dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests avec le compilateur MinGW sous Windows.
Les fichiers gcc_build_all_debug.sh et gcc_build_all_release.sh présents dans le dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests avec GCC sous Linux.
Enfin, les fichiers osx_build_all_debug.sh et osx_build_all_release.sh présents dans le dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests sous Mac (merci à Dominique Billet pour l'écriture des scripts).

Remarque : suivant l'environnement de développement, il peut être nécessaire de modifier le fichier QxOrm.pri pour paramétrer la configuration de la bibliothèque boost :

QX_BOOST_INCLUDE_PATH = $$quote(D:/Dvlp/_Libs/Boost/1_42/include)
QX_BOOST_LIB_PATH = $$quote(D:/Dvlp/_Libs/Boost/1_42/lib_shared)
QX_BOOST_LIB_SERIALIZATION_DEBUG = "boost_serialization-vc90-mt-gd-1_42"
QX_BOOST_LIB_SERIALIZATION_RELEASE = "boost_serialization-vc90-mt-1_42"


Quelles sont les bases de données prises en compte par QxOrm ?

QxOrm utilise le moteur QtSql de Qt basé sur un système de plugin.
Une liste détaillée des bases de données supportées est disponible sur le site de Qt en cliquant ici.
Le plugin ODBC (QODBC) assure une compatibilité avec de nombreuses bases de données.
Pour des performances optimales, il est conseillé d'utiliser un plugin spécifique à une base de données :
  • QMYSQL : MySQL ;
  • QPSQL : PostgreSQL (versions 7.3 and above) ;
  • QOCI : Oracle Call Interface Driver ;
  • QSQLITE : SQLite version 3 ;
  • QDB2 : IBM DB2 (version 7.1 and above) ;
  • QIBASE : Borland InterBase ;
  • QTDS : Sybase Adaptive Server.

Pourquoi QxOrm est dépendant de deux bibliothèques : boost et Qt ?

QxOrm utilise de nombreuses fonctionnalités disponibles dans les excellentes bibliothèques boost et Qt.
De plus, ces deux bibliothèques sont utilisées dans de nombreux projets à la fois professionnels et open-source.
Il existe un grand nombre de forums, de tutoriaux, et toute une communauté pour répondre à toutes les problématiques que vous pourriez rencontrer.
L'objectif de QxOrm n'est pas de redévelopper des fonctionnalités qui existent déjà mais de fournir un outil performant d'accès aux bases de données comme il en existe dans d'autres langages (Java avec Hibernate, .Net avec NHibernate, Ruby, Python, etc.).

boost boost : de nombreux modules de la bibliothèque boost font partie de la nouvelle norme C++.
C'est une bibliothèque reconnue pour sa qualité, son code 'C++ moderne', sa documentation, sa portabilité, etc.
QxOrm utilise les fonctionnalités suivantes de boost : smart_pointer, serialization, type_traits, multi_index_container, unordered_container, any, tuple, foreach, function.
Il est conseillé d'installer et d'utiliser la dernière version de boost disponible à l'adresse suivante : http://www.boost.org/

Qt Qt : bibliothèque complète : IHM (QtGui), réseau (QtNetwork), XML (QtXml), base de données (QtSql), etc.
La documentation est excellente et le code C++ écrit à partir de cette bibliothèque est à la fois performant et simple de compréhension.
Depuis le rachat par Nokia et sa nouvelle licence LGPL, Qt est sans contexte la bibliothèque phare du moment.
QxOrm est compatible avec les principaux objets définis par Qt : QObject, QString, QDate, QTime, QDateTime, QList, QHash, QSharedPointer, QScopedPointer, etc.
Il est conseillé d'installer et d'utiliser la dernière version de Qt disponible à l'adresse suivante : http://qt.nokia.com/


Pourquoi QxOrm nécessite un en-tête précompilé (precompiled header) pour pouvoir être utilisé ?

QxOrm utilise les techniques de métaprogrammation C++ pour fournir une grande partie de ses fonctionnalités.
Vous n'avez pas besoin de savoir utiliser la métaprogrammation pour travailler avec la bibliothèque QxOrm.
En effet, QxOrm se veut simple d'utilisation et un code C++ écrit avec Qt et QxOrm est facile à lire, donc facile à développer et à maintenir.

Cependant, la métaprogrammation est couteuse en temps de compilation.
En utilisant un fichier precompiled.h, un projet C++ se compilera beaucoup plus vite.
Un seul fichier d'en-tête est nécessaire pour disposer de l'ensemble des fonctionnalités de QxOrm : le fichier QxOrm.h.


Est-il possible d'accélérer les temps de compilation d'un projet ?

Oui, si la serialization des données au format XML n'est pas utilisée dans le projet, vous pouvez désactiver cette fonctionnalité.
Les temps de compilation seront alors réduits mais vous n'aurez plus accès au namespace qx::serialization:xml.
Pour désactiver la serialization XML, il faut ouvrir le fichier QxConfig.h et modifier la constante _QX_SERIALIZE_XML.
Une recompilation de la bibliothèque QxOrm est nécessaire pour prendre en compte cette modification.

Une autre possibilité est d'utiliser les classes polymorphiques de la bibliothèque boost::serialization (à la place des classes template).
Cette fonctionnalité réduit les temps de compilation ainsi que la taille de l'éxecutable généré.
En contre-partie, la vitesse d'exécution du programme sera réduite puisqu'une partie du travail effectué lors de la compilation devra être réalisé à l'exécution de l'application.
Pour activer cette fonctionnalité dans QxOrm, vous devez modifier la constante _QX_SERIALIZE_POLYMORPHIC du fichier QxConfig.h.
Attention : les fonctions de serialization seront alors accessibles depuis les namespace suivants : qx::serialization::polymorphic_binary, qx::serialization::polymorphic_text et qx::serialization::polymorphic_xml.
Une recompilation de la bibliothèque QxOrm est nécessaire pour prendre en compte cette modification.

Enfin, il est également possible d'utiliser la macro Q_PROPERTY pour déclarer les propriétés si la classe hérite du type QObject.
Dans ce cas, il existe deux manières différentes pour enregistrer les propriétés dans le contexte QxOrm, dont une qui réduit sensiblement les temps de compilation du projet.
Pour plus d'informations sur cette fonctionnalité, rendez-vous sur cette Question-Réponse de la FAQ.


Quels sont les différents types de serialization disponibles ?

QxOrm utilise le framework de serialization proposé par la bibliothèque boost.
Il existe plusieurs types de serialization disponibles : binaire, XML, texte, etc.
Le fichier QxConfig.h permet d'activer et/ou désactiver les différents types de serialization.

Chaque type de serialization possède ses propres caractéristiques :
  • binary : smallest, fastest, non-portable ;
  • text : larger, slower, portable ;
  • XML : largest, slowest, portable.
Remarque : le type binary n'est pas portable, ce qui signifie que des données ne peuvent pas s'échanger entre un sytème Windows et un système Unix par exemple.
Si vous devez faire transiter des données sur un réseau à travers plusieurs systèmes d'exploitation, il faut utiliser le type de serialization text ou XML.
QxOrm propose également une autre solution : le type de serialization portable_binary.
Le type portable_binary possède les mêmes caractéristiques que le type binary et permet d'échanger des données de manière portable.
Cependant, ce type de serialization n'est pas proposé officiellement par la bibliothèque boost, il est donc nécessaire de tester avant de l'utiliser dans un logiciel en production.


Pourquoi QxOrm fournit un nouveau type de container qx::QxCollection<Key, Value> ?

Il existe de nombreux container dans les bibliothèques stl, boost et Qt.
Il est donc légitime de se poser cette question : à quoi sert qx::QxCollection<Key, Value> ?
qx::QxCollection<Key, Value> est un nouveau container (basé sur l'excellente bibliothèque boost::multi_index_container) qui possède les fonctionnalités suivantes :
  • conserve l'ordre d'insertion des éléments dans la liste ;
  • accès rapide à un élément par son index : équivaut à std::vector<T> ou QList<T> par exemple ;
  • accès rapide à un élément par une clé (hash-map) : équivaut à QHash<Key, Value> ou boost::unordered_map<Key, Value> par exemple ;
  • fonctions de tri sur le type Key et sur le type Value.
Remarque : qx::QxCollection<Key, Value> est compatible avec la macro foreach fournie par la bibliothèque Qt ainsi que par la macro BOOST_FOREACH fournie par la bibliothèque boost.
Cependant, chaque élément renvoyé par ces deux macros correspond à un objet de type std::pair<Key, Value>.
Pour obtenir un résultat 'plus naturel' et plus lisible, il est conseillé d'utiliser la macro _foreach : cette macro utilise BOOST_FOREACH pour tous les container sauf pour qx::QxCollection<Key, Value>.
Dans ce cas, l'élément renvoyé correspond au type Value (voir par la suite l'exemple d'utilisation).
La macro _foreach est donc compatible avec tous les container (stl, Qt, boost, etc.) puisqu'elle utilise la macro BOOST_FOREACH.

Autre Remarque : qx::QxCollection<Key, Value> est particulièrement adapté pour recevoir des données issues d'une base de données.
En effet, ces données peuvent être triées (en utilisant ORDER BY dans une requête SQL par exemple), il est donc important de conserver l'ordre d'insertion des éléments dans la liste.
De plus, chaque donnée issue d'une base de données possède un identifiant unique. Il est donc intéressant de pouvoir accéder à un élément en fonction de cet identifiant unique de manière extrèmement rapide (hash-map).

Exemple d'utilisation de la collection qx::QxCollection<Key, Value> :

/* définition d'une classe drug avec 3 propriétés : 'code', 'name', 'description' */
class drug { public: QString code; QString name; QString desc; };

/* pointeur intelligent associé à la classe drug */
typedef boost::shared_ptr<drug> drug_ptr;

/* collection de drugs (accès rapide à un élément de la collection par la propriété 'code') */
qx::QxCollection<QString, drug_ptr> lstDrugs;

/* création de 3 nouveaux drugs */
drug_ptr d1; d1.reset(new drug()); d1->code = "code1"; d1->name = "name1"; d1->desc = "desc1";
drug_ptr d2; d2.reset(new drug()); d2->code = "code2"; d2->name = "name2"; d2->desc = "desc2";
drug_ptr d3; d3.reset(new drug()); d3->code = "code3"; d3->name = "name3"; d3->desc = "desc3";

/* insertion des 3 drugs dans la collection */
lstDrugs.insert(d1->code, d1);
lstDrugs.insert(d2->code, d2);
lstDrugs.insert(d3->code, d3);

/* parcours la collection en utilisant le mot-clé '_foreach' */
_foreach(drug_ptr p, lstDrugs)
{ qDebug() << qPrintable(p->name) << " " << qPrintable(p->desc); }

/* parcours la collection en utilisant une boucle 'for' */
for (long l = 0; l < lstDrugs.count(); ++l)
{
   drug_ptr p = lstDrugs.getByIndex(l);
   QString code = lstDrugs.getKeyByIndex(l);
   qDebug() << qPrintable(p->name) << " " << qPrintable(p->desc);
}

/* parcours la collection en utilisant le style Java avec 'QxCollectionIterator' */
qx::QxCollectionIterator<QString, drug_ptr> itr(lstDrugs);
while (itr.next())
{
   QString code = itr.key();
   qDebug() << qPrintable(itr.value()->name) << " " << qPrintable(itr.value()->desc);
}

/* effectue un tri croissant par clé (propriété 'code') et décroissant par valeur */
lstDrugs.sortByKey(true);
lstDrugs.sortByValue(false);

/* accès rapide à un drug par son 'code' */
drug_ptr p = lstDrugs.getByKey("code2");

/* accès rapide à un drug par son index (position) dans la collection */
drug_ptr p = lstDrugs.getByIndex(2);

/* teste si un drug existe dans la collection et si la liste est vide */
bool bExist = lstDrugs.exist("code3");
bool bEmpty = lstDrugs.empty();

/* supprime de la collection le 2ème élément */
lstDrugs.removeByIndex(2);

/* supprime de la collection l'élément avec le code 'code3' */
lstDrugs.removeByKey("code3");

/* efface tous les éléments de la collection */
lstDrugs.clear();


Pourquoi QxOrm fournit un nouveau type de pointeur intelligent qx::dao::ptr<T> ?

QxOrm est compatible avec les pointeurs intelligents des bibliothèques boost et Qt.
Le pointeur intelligent développé par QxOrm est basé sur QSharedPointer et apporte de nouvelles fonctionnalités s'il est utilisé avec les fonctions 'qx::dao::...'.
qx::dao::ptr<T> conserve automatiquement les valeurs issues de la base de données.
Il est ainsi possible de vérifier à tout moment si une instance d'objet a subi des modifications grâce à la méthode 'isDirty()' : cette méthode peut renvoyer la liste de toutes les propriétés ayant été modifiées.
qx::dao::ptr<T> peut également être utilisé par la fonction 'qx::dao::update_optimized()' pour mettre à jour en base de données uniquement les champs modifiés.
qx::dao::ptr<T> peut être utilisé avec un objet simple ou bien avec la plupart des containers : stl, boost, Qt et qx::QxCollection<Key, Value>.

Exemple d'utilisation du pointeur intelligent qx::dao::ptr<T> :

// exemple d'utilisation de la méthode 'isDirty()'
qx::dao::ptr<blog> blog_isdirty = qx::dao::ptr<blog>(new blog());
blog_isdirty->m_id = blog_1->m_id;
daoError = qx::dao::fetch_by_id(blog_isdirty);
qAssert(! daoError.isValid() && ! blog_isdirty.isDirty());

blog_isdirty->m_text = "blog property 'text' modified => blog is dirty !!!";
QStringList lstDiff; bool bDirty = blog_isdirty.isDirty(lstDiff);
qAssert(bDirty && (lstDiff.count() == 1) && (lstDiff.at(0) == "blog_text"));
if (bDirty) { qDebug("[QxOrm] test dirty 1 : blog is dirty => '%s'", qPrintable(lstDiff.join("|"))); }

// met à jour uniquement la propriété 'm_text' de l'instance 'blog_isdirty'
daoError = qx::dao::update_optimized(blog_isdirty);
qAssert(! daoError.isValid() && ! blog_isdirty.isDirty());
qx::dump(blog_isdirty);

// exemple d'utilisation de la méthode 'isDirty()' avec une liste d'objets
typedef qx::dao::ptr< QList<author_ptr> > type_lst_author_test_is_dirty;

type_lst_author_test_is_dirty container_isdirty = type_lst_author_test_is_dirty(new QList<author_ptr>());
daoError = qx::dao::fetch_all(container_isdirty);
qAssert(! daoError.isValid() && ! container_isdirty.isDirty() && (container_isdirty->count() == 3));

author_ptr author_ptr_dirty = container_isdirty->at(1);
author_ptr_dirty->m_name = "author name modified at index 1 => container is dirty !!!";
bDirty = container_isdirty.isDirty(lstDiff);
qAssert(bDirty && (lstDiff.count() == 1));
if (bDirty) { qDebug("[QxOrm] test dirty 2 : container is dirty => '%s'", qPrintable(lstDiff.join("|"))); }

author_ptr_dirty = container_isdirty->at(2);
author_ptr_dirty->m_birthdate = QDate(1998, 03, 06);
bDirty = container_isdirty.isDirty(lstDiff);
qAssert(bDirty && (lstDiff.count() == 2));
if (bDirty) { qDebug("[QxOrm] test dirty 3 : container is dirty => '%s'", qPrintable(lstDiff.join("|"))); }

// met à jour la propriété 'm_name' en position 1, la propriété 'm_birthdate' en position 2 et ne change rien en position 0
daoError = qx::dao::update_optimized(container_isdirty);
qAssert(! daoError.isValid() && ! container_isdirty.isDirty());
qx::dump(container_isdirty);

// récupère uniquement la propriété 'm_dt_creation' du blog
QStringList lstColumns = QStringList() << "date_creation";
list_blog lst_blog_with_only_date_creation;
daoError = qx::dao::fetch_all(lst_blog_with_only_date_creation, NULL, lstColumns);
qAssert(! daoError.isValid() && (lst_blog_with_only_date_creation.size() > 0));

if ((lst_blog_with_only_date_creation.size() > 0) && (lst_blog_with_only_date_creation[0] != NULL))
{ qAssert(lst_blog_with_only_date_creation[0]->m_text.isEmpty()); }

qx::dump(lst_blog_with_only_date_creation);


Faut-il utiliser QString ou std::string ?

QxOrm conseille d'utiliser la classe QString pour la gestion des chaînes de caractères.
Même si boost fournit de nombreuses fonctionnalités avec son module boost::string_algo, la classe QString est plus simple à utiliser et surtout prend en charge automatiquement les différents formats : Ascii, Utf8, Utf16, etc.
Cependant, QxOrm est compatible avec std::string et std::wstring si vous préférez utiliser ce type de chaîne de caractères.


Faut-il utiliser les pointeurs intelligents smart-pointer ?

QxOrm conseille fortement d'utiliser les pointeurs intelligents de boost ou Qt.
Le langage C++ ne possède pas de Garbage Collector comme Java ou C# par exemple.
L'utilisation des smart-pointer simplifie énormément la gestion de la mémoire en C++.
L'idéal dans un programme C++ est de n'avoir aucun appel à delete ou delete[].
De plus, les smart-pointer font partie de la nouvelle norme C++ : C++1x.
Il est donc essentiel aujourd'hui de connaître les classes suivantes :


La clé primaire est de type long par défaut. Est-il possible d'utiliser une clé de type QString ou autre ?

Il est possible de définir un identifiant unique de type QString ou autre avec la bibliothèque QxOrm.
Par défaut, l'identifiant unique est de type long.
Pour indiquer qu'une classe a un identifiant unique de type QString ou autre, il faut spécialiser le template qx::trait::get_primary_key.
Pour simplifier, vous pouvez utiliser la macro : QX_REGISTER_PRIMARY_KEY(myClass, QString).

Attention : la macro QX_REGISTER_PRIMARY_KEY doit être utilisée avant la macro QX_REGISTER_HPP_... dans la définition de la classe, sinon une erreur de compilation se produit.


Comment définir une clé primaire sur plusieurs colonnes (composite key) ?

QxOrm supporte la notion de 'multi-columns primary key'.
L'identifiant de la classe doit être du type suivant :
  • QPair ou std::pair pour définir deux colonnes ;
  • boost::tuple pour définir de deux à neuf colonnes.
Il est nécessaire d'utiliser la macro QX_REGISTER_PRIMARY_KEY() pour spécialiser le template et ainsi définir le type d'identifiant sur plusieurs colonnes.
La liste des noms des colonnes doit être de la forme suivante : 'column1|column2|column3|etc.'.

Exemple d'utilisation avec la classe 'author' du projet 'qxBlogCompositeKey', cette classe possède un identifiant sur trois colonnes :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_

class blog;

class QX_BLOG_DLL_EXPORT author
{

   QX_REGISTER_FRIEND_CLASS(author)

public:

// -- clé composée (clé primaire définie sur plusieurs colonnes dans la base de données)
   typedef boost::tuple<QString, long, QString> type_composite_key;
   static QString str_composite_key() { return "author_id_0|author_id_1|author_id_2"; }

// -- typedef
   typedef boost::shared_ptr<blog> blog_ptr;
   typedef std::vector<blog_ptr> list_blog;

// -- enum
   enum enum_sex { male, female, unknown };

// -- propriétés
   type_composite_key   m_id;
   QString              m_name;
   QDate                m_birthdate;
   enum_sex             m_sex;
   list_blog            m_blogX;

// -- constructeur, destructeur virtuel
   author() : m_id("", 0, ""), m_sex(unknown) { ; }
   virtual ~author() { ; }

// -- méthodes
   int age() const;

// -- méthodes d'accès à la clé composée
   type_composite_key getId() const    { return m_id; }
   QString getId_0() const             { return boost::tuples::get<0>(m_id); }
   long getId_1() const                { return boost::tuples::get<1>(m_id); }
   QString getId_2() const             { return boost::tuples::get<2>(m_id); }

// -- méthodes de modification de la clé composée
   void setId_0(const QString & s)     { boost::tuples::get<0>(m_id) = s; }
   void setId_1(long l)                { boost::tuples::get<1>(m_id) = l; }
   void setId_2(const QString & s)     { boost::tuples::get<2>(m_id) = s; }

};

QX_REGISTER_PRIMARY_KEY(author, author::type_composite_key)
QX_REGISTER_HPP_QX_BLOG(author, qx::trait::no_base_class_defined, 0)

typedef boost::shared_ptr<author> author_ptr;
typedef qx::QxCollection<author::type_composite_key, author_ptr> list_author;

#endif // _QX_BLOG_AUTHOR_H_

#include "../include/precompiled.h"
#include "../include/author.h"
#include "../include/blog.h"
#include <QxMemLeak.h>

QX_REGISTER_CPP_QX_BLOG(author)

namespace qx {
template <> void register_class(QxClass<author> & t)
{
   t.id(& author::m_id, author::str_composite_key());

   t.data(& author::m_name, "name");
   t.data(& author::m_birthdate, "birthdate");
   t.data(& author::m_sex, "sex");

   t.relationOneToMany(& author::m_blogX, blog::str_composite_key(), author::str_composite_key());

   t.fct_0<int>(& author::age, "age");
}}

int author::age() const
{
   if (! m_birthdate.isValid()) { return -1; }
   return (QDate::currentDate().year() - m_birthdate.year());
}


Comment activer/désactiver le module QxMemLeak pour la détection automatique des fuites mémoires ?

Le module QxMemLeak permet une détection rapide des fuites mémoire en mode Debug une fois l'exécution du programme terminée (avec indication du fichier et de la ligne => style MFC de Microsoft).
Ce module a été développé par Wu Yongwei et a subi quelques modifications pour être intégré dans QxOrm.
Si un autre outil est déjà utilisé (Valgrind par exemple), cette fonctionnalité ne doit pas être activée.
Pour activer/désactiver le module QxMemLeak, il suffit de modifier la constante _QX_USE_MEM_LEAK_DETECTION définie dans le fichier QxConfig.h.
Une recompilation de la bibliothèque QxOrm est nécessaire pour prendre en compte cette modification.


Comment gérer la notion d'héritage avec la base de données ?

On retrouve généralement dans les différents outils de type ORM trois différentes stratégies pour gérer la notion d'héritage avec la base de données : QxOrm utilise par défaut la stratégie Concrete Table Inheritance (les autres stratégies ne sont pas fonctionnelles à l'heure actuelle).
De nombreux tutoriaux et forums sont disponibles sur internet pour plus de détails sur cette notion d'héritage.
Un exemple d'utilisation avec une classe de base se trouve dans le dossier ./test/qxDllSample/dll2/ avec la classe BaseClassTrigger.


Comment utiliser les Trigger ?

Les Trigger de QxOrm permettent d'effectuer divers traitements avant et/ou après une insertion, une mise à jour ou bien une suppression dans la base de données.
Un exemple d'utilisation se trouve dans le dossier ./test/qxDllSample/dll2/ avec la classe BaseClassTrigger.
Cette classe contient cinq propriétés : m_id, m_dateCreation, m_dateModification, m_userCreation et m_userModification.
Ces propriétés se mettront à jour automatiquement pour chaque classe héritant de BaseClassTrigger (cf. les classes Foo et Bar du même projet).
Il est nécessaire de spécialiser le template 'QxDao_Trigger' pour profiter de cette fonctionnalité.

#ifndef _QX_BASE_CLASS_TRIGGER_H_
#define _QX_BASE_CLASS_TRIGGER_H_

class QX_DLL2_EXPORT BaseClassTrigger
{

   QX_REGISTER_FRIEND_CLASS(BaseClassTrigger)

protected:

   long        m_id;
   QDateTime   m_dateCreation;
   QDateTime   m_dateModification;
   QString     m_userCreation;
   QString     m_userModification;

public:

   BaseClassTrigger() : m_id(0)  { ; }
   virtual ~BaseClassTrigger()   { ; }

   long getId() const                     { return m_id; }
   QDateTime getDateCreation() const      { return m_dateCreation; }
   QDateTime getDateModification() const  { return m_dateModification; }
   QString getUserCreation() const        { return m_userCreation; }
   QString getUserModification() const    { return m_userModification; }

   void setId(long l)                              { m_id = l; }
   void setDateCreation(const QDateTime & dt)      { m_dateCreation = dt; }
   void setDateModification(const QDateTime & dt)  { m_dateModification = dt; }
   void setUserCreation(const QString & s)         { m_userCreation = s; }
   void setUserModification(const QString & s)     { m_userModification = s; }

   void onBeforeInsert(qx::dao::detail::IxDao_Helper * dao);
   void onBeforeUpdate(qx::dao::detail::IxDao_Helper * dao);

};

QX_REGISTER_HPP_QX_DLL2(BaseClassTrigger, qx::trait::no_base_class_defined, 0)

namespace qx {
namespace dao {
namespace detail {

template <>
struct QxDao_Trigger<BaseClassTrigger>
{

   static inline void onBeforeInsert(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { if (t) { t->onBeforeInsert(dao); } }
   static inline void onBeforeUpdate(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { if (t) { t->onBeforeUpdate(dao); } }
   static inline void onBeforeDelete(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onAfterInsert(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onAfterUpdate(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }
   static inline void onAfterDelete(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
   { Q_UNUSED(t); Q_UNUSED(dao); }

};

} // namespace detail
} // namespace dao
} // namespace qx

#endif // _QX_BASE_CLASS_TRIGGER_H_

#include "../include/precompiled.h"
#include "../include/BaseClassTrigger.h"
#include <QxMemLeak.h>

QX_REGISTER_CPP_QX_DLL2(BaseClassTrigger)

namespace qx {
template <> void register_class(QxClass<BaseClassTrigger> & t)
{
   IxDataMember * pData = NULL;

   pData = t.id(& BaseClassTrigger::m_id, "id");

   pData = t.data(& BaseClassTrigger::m_dateCreation, "date_creation");
   pData = t.data(& BaseClassTrigger::m_dateModification, "date_modification");
   pData = t.data(& BaseClassTrigger::m_userCreation, "user_creation");
   pData = t.data(& BaseClassTrigger::m_userModification, "user_modification");
}}

void BaseClassTrigger::onBeforeInsert(qx::dao::detail::IxDao_Helper * dao)
{
   Q_UNUSED(dao);
   m_dateCreation = QDateTime::currentDateTime();
   m_dateModification = QDateTime::currentDateTime();
   m_userCreation = "current_user_1";
   m_userModification = "current_user_1";
}

void BaseClassTrigger::onBeforeUpdate(qx::dao::detail::IxDao_Helper * dao)
{
   Q_UNUSED(dao);
   m_dateModification = QDateTime::currentDateTime();
   m_userModification = "current_user_2";
}


Comment déclarer une classe abstraite dans le contexte QxOrm ?

Une classe abstraite C++ (contenant au moins une méthode virtuelle pure) ne peut pas être mappée avec une table d'une base de données (puisqu'elle ne peut pas être instanciée).
Cependant, il peut être intéressant de définir une classe abstraite contenant une liste de propriétés utilisées par plusieurs objets persistants.
Un exemple de classe abstraite se trouve dans le dossier ./test/qxDllSample/dll2/ de la distribution de QxOrm avec la classe BaseClassTrigger.
QxOrm propose le mécanisme suivant pour définir une classe abstraite dans le contexte QxOrm :
  • déclarer la classe avec la méthode 'void register_class' comme n'importe qu'elle autre classe ;
  • utiliser la macro QX_REGISTER_ABSTRACT_CLASS(className) juste après la définition de la classe.


Comment déclarer une classe définie dans un espace de nom (namespace) dans le contexte QxOrm ?

Si une classe est définie dans un espace de nom (namespace), alors une erreur de compilation se produit avec l'utilisation des macros : QX_REGISTER_HPP et QX_REGISTER_CPP.
Pour éviter ces erreurs de compilation, il est nécessaire d'utiliser les macros suivantes : QX_REGISTER_COMPLEX_CLASS_NAME_HPP et QX_REGISTER_COMPLEX_CLASS_NAME_CPP.
Vous trouverez un exemple d'utilisation dans le dossier ./test/qxDllSample/dll1/ de la distribution de QxOrm avec la classe CPerson définie dans l'espace de nom qx::test :

* QX_REGISTER_COMPLEX_CLASS_NAME_HPP_QX_DLL1(qx::test::CPerson, QObject, 0, qx_test_CPerson)


Comment utiliser le mécanisme de suppression logique (soft delete) ?

Une suppression logique permet de ne pas effacer de ligne dans une table d'une base de données (contrairement à une suppression physique) : une colonne supplémentaire est ajoutée à la définition de la table pour indiquer que la ligne est supprimée ou non.
Cette colonne peut contenir soit un booléen (1 signifie ligne supprimée, 0 ou vide signifie ligne non supprimée), soit la date-heure de suppression de la ligne (si vide, la ligne est considérée comme non supprimée).
Il est donc à tout moment possible de réactiver une ligne supprimée en réinitialisant la valeur à vide dans la table de la base de données.

Pour activer le mécanisme de suppression logique avec la bibliothèque QxOrm, il faut utiliser la classe qx::QxSoftDelete dans la fonction de mapping qx::register_class<T>.
Voici un exemple d'utilisation avec une classe Bar contenant deux propriétés m_id et m_desc :

namespace qx {
template <> void register_class(QxClass<Bar> & t)
{
   t.setSoftDelete(qx::QxSoftDelete("deleted_at"));

   t.id(& Bar::m_id, "id");
   t.data(& Bar::m_desc, "desc");
}}

Les requêtes SQL générées automatiquement par la bibliothèque QxOrm vont prendre en compte ce paramètre de suppression logique pour ajouter les conditions nécessaires (ne pas récupérer les éléments supprimés, ne pas supprimer physiquement une ligne, etc.).
Par exemple, si vous exécutez les lignes suivantes avec la classe Bar :

Bar_ptr pBar; pBar.reset(new Bar());
pBar->setId(5);
QSqlError daoError = qx::dao::delete_by_id(pBar);     qAssert(! daoError.isValid());
qx_bool bDaoExist = qx::dao::exist(pBar);             qAssert(! bDaoExist);
daoError = qx::dao::delete_all<Bar>();                qAssert(! daoError.isValid());
long lBarCount = qx::dao::count<Bar>();               qAssert(lBarCount == 0);
daoError = qx::dao::destroy_all<Bar>();               qAssert(! daoError.isValid());

Vous obtiendrez les traces suivantes :

[QxOrm] sql query (93 ms) : UPDATE Bar SET deleted_at = '20110617115148615' WHERE id = :id
[QxOrm] sql query (0 ms) : SELECT Bar.id AS Bar_id_0, Bar.deleted_at FROM Bar WHERE Bar.id = :id AND (Bar.deleted_at IS NULL OR Bar.deleted_at = '')
[QxOrm] sql query (78 ms) : UPDATE Bar SET deleted_at = '20110617115148724'
[QxOrm] sql query (0 ms) : SELECT COUNT(*) FROM Bar WHERE (Bar.deleted_at IS NULL OR Bar.deleted_at = '')
[QxOrm] sql query (110 ms) : DELETE FROM Bar

Remarque : pour supprimer physiquement une ligne de la base de données, il faut utiliser les fonctions : qx::dao::destroy_by_id() et qx::dao::destroy_all().

Autre remarque : il peut être intéressant de définir au niveau du SGBD un index sur la colonne deleted_at (ou peu importe le nom que vous donnez) afin d'accélérer l'exécution des requêtes SQL.


Comment utiliser les sessions (classe qx::QxSession) pour simplifier la gestion des transactions des bases de données (C++ RAII) ?

Une transaction est une suite d'opérations effectuées comme une seule unité logique de travail.
Une fois terminée, la transaction est :
* soit validée (commit), alors toutes les modifications sont faites dans la base de données ;
* soit annulée (rollback), alors toutes les modifications ne sont pas enregistrée.

La classe qx::QxSession de la bibliothèque QxOrm permet de gérer automatiquement les transactions (validation, annulation) en utilisant le mécanisme C++ RAII :

{ // Ouverture d'un scope où une session sera instanciée

  // Création d'une session : une connection valide à la BDD est assignée à la session et une transaction est démarrée
  qx::QxSession session;

  // Exécution d'une série d'opérations avec la BDD (en utilisant l'opérateur += de la classe qx::QxSession et la connection de la session)
  session += qx::dao::insert(my_object, session.database());
  session += qx::dao::update(my_object, session.database());
  session += qx::dao::fetch_by_id(my_object, session.database());
  session += qx::dao::delete_by_id(my_object, session.database());

  // Si la session n'est pas valide (donc une erreur s'est produite) => affichage de la 1ère erreur de la session
  if (! session.isValid()) { qDebug("[QxOrm] session error : '%s'", qPrintable(session.firstError().text())); }

} // Fermeture du scope : la session est détruite (transaction => commit ou rollback automatique)

Remarque : une session peut déclencher une exception de type qx::dao::sql_error lorsqu'une erreur se produit (par défaut, aucune exception n'est déclenchée). Il est possible de paramétrer ce comportement en utilisant :
* soit le constructeur de la classe qx::QxSession (pour une session en particulier) ;
* soit le paramètre du singleton qx::QxSqlDatabase::getSingleton()->setSessionThrowable(bool b) (pour toutes les sessions).

Autre remarque : il est important de ne pas oublier de passer la connection à la base de données de la session à chaque fonction qx::dao::xxx (en utilisant la méthode session.database()).
De plus, il est possible d'initialiser une session avec sa propre connection (provenant d'un pool de connections par exemple) en utilisant le constructeur de la classe qx::QxSession.


Comment persister un type dont on ne possède pas le code source (classe provenant d'une bibliothèque tierce par exemple) ?

La bibliothèque QxOrm permet de persister n'importe quel type, même si ce dernier n'est pas enregistré dans le contexte QxOrm par la méthode qx::register_class<T>().

Il est nécessaire d'écrire les fonctions de sérialisation de la bibliothèque boost, en utilisant la méthode non intrusive (puisque le code source n'est pas disponible ou ne peut pas être modifié). Pour plus d'informations sur la sérialisation des données avec la bibliothèque boost, rendez-vous sur le tutoriel de developpez.com.

Par exemple, imaginons une classe 'ExtObject3D' provenant d'une bibliothèque tierce et dont le code source n'est pas disponible ou ne peut pas être modifié. Voici le code nécessaire pour pouvoir persister une instance de type 'ExtObject3D' en base de données :

#ifndef _PERSIST_EXTOBJECT3D_H_
#define _PERSIST_EXTOBJECT3D_H_

#include "ExtObject3D.h"

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/nvp.hpp>
 
namespace boost {
namespace serialization {

template <class Archive>
void save(Archive & ar, const ExtObject3D & t, unsigned int version)
{
   Q_UNUSED(version);
   double x(t.getX()), y(t.getY()), z(t.getZ()), angle(t.getAngle());

   ar << boost::serialization::make_nvp("x", x);
   ar << boost::serialization::make_nvp("y", y);
   ar << boost::serialization::make_nvp("z", z);
   ar << boost::serialization::make_nvp("angle", angle);
}

template <class Archive>
void load(Archive & ar, ExtObject3D & t, unsigned int version)
{
   Q_UNUSED(version);
   double x(0.0), y(0.0), z(0.0), angle(0.0);

   ar >> boost::serialization::make_nvp("x", x);
   ar >> boost::serialization::make_nvp("y", y);
   ar >> boost::serialization::make_nvp("z", z);
   ar >> boost::serialization::make_nvp("angle", angle);

   t.setX(x);
   t.setY(y);
   t.setZ(z);
   t.setAngle(angle);
}

} // namespace serialization
} // namespace boost
 
BOOST_SERIALIZATION_SPLIT_FREE(ExtObject3D)

#endif // _PERSIST_EXTOBJECT3D_H_

Le code ci-dessus est suffisant pour persister une instance de type 'ExtObject3D' en base de données : il est ainsi possible d'utiliser une propriété de type 'ExtObject3D' dans une classe persistante enregistrée dans le contexte QxOrm. Cette propriété peut être mappée sur une colonne de type TEXT ou VARCHAR en base de données.

Le comportement par défaut de la bibliothèque QxOrm est le suivant : l'instance est sérialisée au format XML avant d'être insérée ou mise à jour en base de données. Ce comportement par défaut peut être utile par exemple si l'on souhaite enregistrer une collection d'objets sans vouloir faire de relation (et donc gérer une autre table dans la base de données). Par exemple, si l'on utilise une propriété de type std::vector<mon_objet> dans une classe persistante sans relation associée, la liste d'éléments sera automatiquement enregistrée au format XML en base de données.

Remarque : ce comportement par défaut peut être facilement modifié pour un type donné. Le moteur QtSql utilise le type QVariant pour faire le lien entre le code C++ et la base de données. Le type QVariant peut contenir du texte, des valeurs numériques, du binaire, etc. Il peut donc être intéressant de spécialiser le comportement par défaut (sérialisation XML) si l'on souhaite stocker des données au format binaire ou bien optimiser les performances (la sérialisation XML peut être couteuse en temps d'exécution). Il suffit de proposer (en plus des fonctions de sérialisation boost) les conversions nécessaires en QVariant, par exemple avec la classe 'ExtObject3D' :

namespace qx {
namespace cvt {
namespace detail {

template <> struct QxStringCvt_ToVariant< ExtObject3D > {
static inline QVariant toVariant(const ExtObject3D & t, const QString & format, int index)
{ /* Ici je convertis ExtObject3D en QVariant */ } };

template <> struct QxStringCvt_FromVariant< ExtObject3D > {
static inline qx_bool fromVariant(const QVariant & v, ExtObject3D & t, const QString & format, int index)
{ /* Ici je convertis QVariant en ExtObject3D */; return qx_bool(true); } };

} // namespace detail
} // namespace cvt
} // namespace qx



Comment utiliser le moteur d'introspection (ou réflexion) de la bibliothèque QxOrm ?

Toute classe enregistrée dans le contexte QxOrm par la méthode qx::register_class<T>() peut être utilisée par le moteur d'introspection (ou réflexion) de la bibliothèque QxOrm. Le moteur d'introspection permet d'obtenir de façon dynamique (donc pendant l'exécution du programme) des informations propres à un type. Ces informations correspondent à des méta-données et décrivent de façon exhaustive les caractéristiques d'une classe (propriétés, méthodes, etc.). De nombreux langages de programmation (par exemple Java ou C#) intègrent nativement ce mécanisme, ce n'est pas le cas du C++, c'est pourquoi la bibliothèque QxOrm émule un moteur d'introspection.

Voici la liste des classes disponibles pour accéder aux méta-données :
  • qx::QxClassX : singleton permettant de parcourir l'ensemble des classes enregistrées dans le contexte QxOrm par la méthode qx::register_class<T>() ;
  • qx::IxClass : interface pour une classe enregistrée dans le contexte QxOrm ;
  • qx::IxDataMemberX : liste des propriétés associées à une classe ;
  • qx::IxDataMember : interface pour une propriété d'une classe ;
  • qx::IxFunctionX : liste des méthodes associées à une classe ;
  • qx::IxFunction : interface pour une méthode d'une classe.
Une instance de type qx::IxClass possède la liste des propriétés d'une classe (qx::IxDataMemberX) ainsi que la liste des méthodes d'une classe (qx::IxFunctionX).

Le moteur d'introspection de la bibliothèque QxOrm permet par exemple de :
  • créer dynamiquement une instance en fonction du nom d'une classe sous forme de chaîne de caractères (qx::create()) ;
  • accéder/modifier le contenu d'un champ d'un objet de façon dynamique en prenant pour paramètres un objet et le nom du champ qu'on souhaite accéder/modifier (qx::IxDataMember::getValue() et qx::IxDataMember::setValue()) ;
  • invoquer une méthode de classe de façon dynamique, en gérant bien entendu le passage des paramètres souhaités à la méthode (qx::IxFunction::invoke()) ;
  • accéder à la hiérarchie d'une classe (qx::IxClass::getBaseClass()).
Remarque : le module QxService de la bibliothèque QxOrm (cliquez ici pour accéder au tutoriel) permettant de créer un serveur d'applications C++ est basé sur le moteur d'introspection pour appeler dynamiquement les méthodes de type service (demande du client) sur le serveur.

Voici un exemple d'utilisation du moteur d'introspection de la bibliothèque QxOrm : comment lister toutes les classes, propriétés et méthodes enregistrées dans le contexte QxOrm ?

QString QxClassX::dumpAllClasses()
{
   QxClassX::registerAllClasses();
   QxCollection<QString, IxClass *> * pAllClasses = QxClassX::getAllClasses();
   if (! pAllClasses) { qAssert(false); return ""; }

   QString sDump;
   long lCount = pAllClasses->count();
   qDebug("[QxOrm] start dump all registered classes (%ld)", lCount);
   _foreach(IxClass * pClass, (* pAllClasses))
   { if (pClass) { sDump += pClass->dumpClass(); } }
   qDebug("[QxOrm] %s", "end dump all registered classes");

   return sDump;
}

QString IxClass::dumpClass() const
{
   QString sDump;
   sDump += "-- class '" + m_sKey + "' (name '" + m_sName + "', ";
   sDump += "description '" + m_sDescription + "', version '" + QString::number(m_lVersion) + "', ";
   sDump += "base class '" + (getBaseClass() ? getBaseClass()->getKey() : "") + "')\n";

   long lCount = (m_pDataMemberX ? m_pDataMemberX->count() : 0);
   sDump += "\t* list of registered properties (" + QString::number(lCount) + ")\n";
   if (m_pDataMemberX)
   {
      IxDataMember * pId = this->getId();
      for (long l = 0; l < lCount; l++)
      {
         IxDataMember * p = m_pDataMemberX->get(l); if (! p) { continue; }
         IxSqlRelation * pRelation = p->getSqlRelation();
         QString sInfos = p->getKey() + ((p == pId) ? QString(" (id)") : QString());
         sInfos += (pRelation ? (QString(" (") + pRelation->getDescription() + QString(")")) : QString());
         sDump += "\t\t" + sInfos + "\n";
      }
   }

   lCount = (m_pFctMemberX ? m_pFctMemberX->count() : 0);
   sDump += "\t* list of registered functions (" + QString::number(lCount) + ")\n";
   if (m_pFctMemberX)
   {
      _foreach_if(IxFunction_ptr p, (* m_pFctMemberX), (p))
      { QString sKey = p->getKey(); sDump += "\t\t" + sKey + "\n"; }
   }

   qDebug("%s", qPrintable(sDump));
   return sDump;
}

Si on utilise la méthode qx::QxClassX::dumpAllClasses() avec le tutoriel qxBlog, voici le résultat obtenu :

[QxOrm] start dump all registered classes (4)
-- class 'author' (name 'author', description '', version '0', base class '')
	* list of registered properties (5)
		author_id (id)
		name
		birthdate
		sex
		list_blog (relation one-to-many)
	* list of registered functions (1)
		age

-- class 'blog' (name 'blog', description '', version '0', base class '')
	* list of registered properties (6)
		blog_id (id)
		blog_text
		date_creation
		author_id (relation many-to-one)
		list_comment (relation one-to-many)
		list_category (relation many-to-many)
	* list of registered functions (0)

-- class 'comment' (name 'comment', description '', version '0', base class '')
	* list of registered properties (4)
		comment_id (id)
		comment_text
		date_creation
		blog_id (relation many-to-one)
	* list of registered functions (0)

-- class 'category' (name 'category', description '', version '0', base class '')
	* list of registered properties (4)
		category_id (id)
		name
		description
		list_blog (relation many-to-many)
	* list of registered functions (0)

[QxOrm] end dump all registered classes

Remarque : il est possible d'ajouter de nouvelles informations au moteur d'introspection en utilisant la notion de property bag. En effet, les classes qx::IxClass, qx::IxDataMember et qx::IxFunction possèdent chacune une liste d'éléments de type QVariant accessibles par clé de type QString (voir la classe qx::QxPropertyBag pour plus de détails sur cette notion).



Comment déclarer automatiquement les méta-propriétés de Qt (définies par la macro Q_PROPERTY) dans le contexte QxOrm ?

Toute classe héritant du type QObject peut déclarer ses propriétés avec la macro Q_PROPERTY : les propriétés deviennent alors des méta-propriétés. Ce mécanisme permet au framework Qt de proposer un moteur d'introspection grâce au pré-compilateur moc. Les méta-propriétés peuvent alors être utilisées par exemple par le moteur QML, QtScript, etc.

La bibliothèque QxOrm nécessite une déclaration de chacune des propriétés d'une classe dans la fonction de mapping void qx::register_class<T>() afin de proposer l'ensemble de ses fonctionnalités (persistance des données, sérialisation XML et binaire, etc.). Il est possible de déclarer automatiquement dans le contexte QxOrm l'ensemble des méta-propriétés sans maintenir une fonction de mapping void qx::register_class<T>() : la macro QX_REGISTER_ALL_QT_PROPERTIES() utilise le moteur d'introspection de Qt pour parcourir la liste des méta-propriétés.

Voici un exemple d'utilisation avec la classe TestQtProperty se trouvant dans le dossier ./test/qxDllSample/dll1/include/ de la distribution QxOrm :

#ifndef _QX_TEST_QT_META_PROPERTY_H_
#define _QX_TEST_QT_META_PROPERTY_H_
 
class QX_DLL1_EXPORT TestQtProperty : public QObject
{

   Q_OBJECT
   Q_PROPERTY(int id READ id WRITE setId)
   Q_PROPERTY(long number READ number WRITE setNumber)
   Q_PROPERTY(QString desc READ desc WRITE setDesc)
   Q_PROPERTY(QDateTime birthDate READ birthDate WRITE setBirthDate)
   Q_PROPERTY(QVariant photo READ photo WRITE setPhoto)

protected:

   int         m_id;
   long        m_number;
   QString     m_desc;
   QDateTime   m_birthDate;
   QVariant    m_photo;

public:

   TestQtProperty() : QObject(), m_id(0), m_number(0) { ; }
   virtual ~TestQtProperty() { ; }

   int id() const                { return m_id; }
   long number() const           { return m_number; }
   QString desc() const          { return m_desc; }
   QDateTime birthDate() const   { return m_birthDate; }
   QVariant photo() const        { return m_photo; }

   void setId(int i)                         { m_id = i; }
   void setNumber(long l)                    { m_number = l; }
   void setDesc(const QString & s)           { m_desc = s; }
   void setBirthDate(const QDateTime & dt)   { m_birthDate = dt; }
   void setPhoto(const QVariant & v)         { m_photo = v; }
 
};

QX_REGISTER_HPP_QX_DLL1(TestQtProperty, QObject, 0)

#endif // _QX_TEST_QT_META_PROPERTY_H_

#include "../include/precompiled.h"

#include "../include/TestQtProperty.h"

#include <QxMemLeak.h>
 
QX_REGISTER_CPP_QX_DLL1(TestQtProperty)
QX_REGISTER_ALL_QT_PROPERTIES(TestQtProperty, "id")

Pour ceux qui ne souhaitent pas utiliser la macro QX_REGISTER_ALL_QT_PROPERTIES, il est possible d'écrire à la place les quatre lignes de code suivantes :

namespace qx {
template <> void register_class(QxClass<TestQtProperty> & t)
{ qx::register_all_qt_properties<TestQtProperty>(t, "id"); }
} // namespace qx

Remarque : le deuxième paramètre de la macro QX_REGISTER_ALL_QT_PROPERTIES permet d'indiquer la propriété qui servira de clé primaire dans la base de données. Si ce paramètre est vide, cela signifie que la classe ne possède pas de clé primaire ou bien que celle-ci est définie dans une classe de base.

Toute propriété définie avec la macro Q_PROPERTY peut s'enregistrer dans le contexte QxOrm de deux manières différentes :
1- par la méthode classique : t.data(& MyQObject::my_property, "my_property", 0);
2- ou bien sans mentionner le pointeur vers la donnée membre de la classe : t.data("my_property", 0);

Peu importe la méthode d'enregistrement des propriétés dans le contexte QxOrm, elles seront accessibles par la même interface qx::IxDataMember et proposent donc les mêmes fonctionnalités. Il est possible d'utiliser les deux méthodes dans une même fonction de mapping void qx::register_class<T>(). Chaque méthode d'enregistrement présente des avantages et inconvénients.

Voici la liste des avantages de la deuxième méthode d'enregistrement des propriétés dans le contexte QxOrm :
  • temps de compilation du projet beaucoup plus rapide ;
  • taille de l'exécutable généré plus petite ;
  • forte intégration avec le moteur d'introspection du framework Qt ;
  • pas besoin de maintenir la fonction de mapping en utilisant la macro QX_REGISTER_ALL_QT_PROPERTIES.
Voici les inconvénients par rapport à la méthode classique d'enregistrement des propriétés :
  • nécessite un héritage de la classe QObject pour pouvoir utiliser la macro Q_PROPERTY ;
  • exécution du programme plus lente (utilisation du type QVariant à la place des template C++) ;
  • ne supporte pas la notion de relation entre tables de la base de données (one-to-one, one-to-many, many-to-one et many-to-many) ;
  • pas d'accès au pointeur sur la donnée membre de la classe (conversion nécessaire au type QVariant pour accéder et modifier une valeur).




QxOrm