QxOrm Windows Linux Macintosh C++

Accueil T�l�chargement Exemple rapide Tutoriel (4)
Manuel (2)
Forum Nos clients

QxOrm >> Faq
Version courante :  QxOrm 1.5.0 - documentation en ligne de la biblioth�que QxOrm - GitHub
QxEntityEditor 1.2.8
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).
QxOrm est d�velopp� XDL Teamarty, 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, XML et JSON) ;
  • 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.


Qu'est-ce que QxEntityEditor ?

QxEntityEditor est un �diteur graphique pour la biblioth�que QxOrm : QxEntityEditor permet de g�rer graphiquement le mod�le d'entit�s.
QxEntityEditor est multi-plateforme (disponible pour Windows, Linux et Mac OS X) et g�n�re du code natif pour tous les environnements : bureau (Windows, Linux, Mac OS X), embarqu� et mobile (Android, iOS, Windows Phone, Raspberry Pi, etc.).
Une vid�o de pr�sentation de l'application QxEntityEditor est disponible.

QxEntityEditor est bas� sur un syst�me de plugins et propose diverses fonctionnalit�s pour importer/exporter le mod�le de donn�es :
  • g�n�ration automatique du code C++ (classes persistantes enregistr�es dans le contexte QxOrm) ;
  • g�n�ration automatique des scripts SQL DDL (sch�ma de base de donn�es) pour les bases SQLite, MySQL, PostgreSQL, Oracle et MS SQL Server ;
  • supporte l'�volution du sch�ma de base de donn�es pour chaque version d'un projet (ALTER TABLE, ADD COLUMN, DROP INDEX, etc.) ;
  • g�n�ration automatique des classes C++ de services pour transf�rer le mod�le de donn�es sur le r�seau, en utilisant le module QxService, pour cr�er rapidement des applications client/serveur ;
  • importation automatique des structures de bases de donn�es existantes (par connexion ODBC) pour les bases SQLite, MySQL, PostgreSQL, Oracle et MS SQL Server ;
  • parce que chaque projet est diff�rent, QxEntityEditor propose plusieurs outils pour personnaliser les fichiers g�n�r�s (notamment un moteur javascript et un d�bogueur int�gr�).
QxEntityEditor

QxEntityEditor est d�velopp� XDL Teamarty, Ing�nieur en d�veloppement logiciel depuis 2003.



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.
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 installer et compiler QxOrm ?

Un tutoriel pour installer un environnement de d�veloppement avec QxOrm sous Windows est disponible en cliquant ici.

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, Visual C++ 2010 et Visual C++ 2012.
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.).

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 puis Digia 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://www.qt.io/

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/


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 de configuration QxOrm.pri et supprimer (ou mettre en commentaire) l'option de compilation _QX_ENABLE_BOOST_SERIALIZATION_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 utiliser cette fonctionnalit� dans QxOrm, vous devez activer l'option de compilation _QX_ENABLE_BOOST_SERIALIZATION_POLYMORPHIC dans le fichier de configuration QxOrm.pri.
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.

Remarque : il est �galement n�cessaire de s'assurer que toutes les optimisations propos�es par le compilateur sont activ�es, notamment au niveau de la compilation parall�le sur plusieurs processeurs :
  • MSVC++ : utiliser la variable d'environnement SET CL=/MP
  • GCC et Clang : pr�ciser le nombre de processeurs utilis�s en param�tre du process make, par exemple pour 8 coeurs : SET MAKE_COMMAND=make -j8


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, JSON, texte, etc.
Le fichier de configuration QxOrm.pri 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 ?

Par d�faut, lorsqu'un mapping d'une classe C++ est �crit avec la m�thode void qx::register_class<T>, l'identifiant associ� � la classe est de type long (cl� primaire avec auto-incr�mentation dans la base de donn�es).

Il est possible de d�finir un identifiant d'un autre type en utilisant la macro QX_REGISTER_PRIMARY_KEY.
Cette macro sp�cialise le template qx::trait::get_primary_key pour associer un type d'identifiant � une classe C++.

Par exemple, pour d�finir un identifiant unique de type QString pour la classe C++ myClass (mapp�e vers une table de la BDD avec une colonne de type VARCHAR pour cl� primaire), il suffit d'�crire :
QX_REGISTER_PRIMARY_KEY(myClass, QString)

Voici un exemple d'utilisation de la macro QX_REGISTER_PRIMARY_KEY avec une classe author poss�dant un identifiant de type QString :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_
 
class author
{
public:
// -- propri�t�s
   QString  m_id;
   QString  m_name;
// -- constructeur, destructeur virtuel
   author() { ; }
   virtual ~author() { ; }
};

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

#endif // _QX_BLOG_AUTHOR_H_


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 <QxOrm_Impl.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 enregistrer des membres private ou protected dans le contexte QxOrm ?

Pour enregistrer des membres private ou protected dans le contexte QxOrm (fonction qx::register_class<T>), il faut d�clarer les friend class n�cessaires.
Pour simplifier l'�criture avec les template C++, la biblioth�que QxOrm fournit la macro suivante : QX_REGISTER_FRIEND_CLASS(myClass).
Un exemple d'utilisation se trouve dans le dossier ./test/qxDllSample/dll1/ du package QxOrm avec la classe CPerson :

namespace qx {
namespace test {

class QX_DLL1_EXPORT CPerson : public QObject
{

   Q_OBJECT
   QX_REGISTER_FRIEND_CLASS(qx::test::CPerson)

   // etc...

};

} // namespace test
} // namespace qx



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 onBeforeFetch(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); }
   static inline void onAfterFetch(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 <QxOrm_Impl.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)

Les macros QX_REGISTER_COMPLEX_CLASS_NAME... n�cessitent un param�tre suppl�mentaire (dans l'exemple ci-dessus il s'agit du param�tre qx_test_CPerson) afin de cr�er une variable globale.
Celle-ci est appel�e d�s le lancement de l'application.
La construction de cette instance globale d�clare la classe dans le module QxFactory (mod�le de conception fabrique ou design pattern factory).
Un objet C++ ne pouvant pas se nommer avec des caract�res "::", le param�tre suppl�mentaire de la macro permet de remplacer tous les "::" par des "_".


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.

La classe qx::QxSession propose �galement des m�thodes de persistance (CRUD), ce qui peut simplifier l'�criture du code C++ suivant les habitudes de programmation.
Voici le m�me exemple en utilisant les m�thodes de la classe qx::QxSession � la place des fonctions du namespace qx::dao :

{ // 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
  session.insert(my_object);
  session.update(my_object);
  session.fetchById(my_object);
  session.deleteById(my_object);

  // 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)



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 QxConvert_ToVariant< ExtObject3D > {
static inline QVariant toVariant(const ExtObject3D & t, const QString & format, int index)
{ /* Ici je convertis ExtObject3D en QVariant */ } };

template <> struct QxConvert_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, JSON 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 <QxOrm_Impl.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).


Comment construire une requ�te pour interroger la base de donn�es sans �crire de SQL avec la classe qx::QxSqlQuery ?

La classe qx::QxSqlQuery (ou bien son alias qx_query) permet d'interroger la base de donn�es (trier, filtrer, etc.) de deux mani�res diff�rentes : Le principal avantage de la premi�re m�thode (�criture manuelle des requ�tes SQL) est de pouvoir utiliser certaines optimisations sp�cifiques � chaque base de donn�es.
La deuxi�me m�thode (utilisation du code C++ pour g�n�rer la requ�te SQL) permet de mapper automatiquement les param�tres SQL sans utiliser la fonction qx::QxSqlQuery::bind().

Voici un exemple d'utilisation de la classe qx::QxSqlQuery avec �criture manuelle d'une requ�te SQL :

// Construit une requ�te pour r�cup�rer uniquement les 'author' de type 'female'
qx::QxSqlQuery query("WHERE author.sex = :sex");
query.bind(":sex", author::female);

QList<author> list_of_female;
QSqlError daoError = qx::dao::fetch_by_query(query, list_of_female);
for (long l = 0; l < list_of_female.count(); l++)
{ /* traitement avec la collection issue de la base de donn�es */ }

La biblioth�que QxOrm supporte trois syntaxes pour l'�criture des param�tres SQL.
Le type de syntaxe peut �tre modifi� de fa�on globale � un projet en utilisant la m�thode suivante : qx::QxSqlDatabase::getSingleton()->setSqlPlaceHolderStyle().
Les trois param�tres possibles pour cette m�thode sont :
  • ph_style_2_point_name : "WHERE author.sex = :sex" (syntaxe par d�faut) ;
  • ph_style_at_name : "WHERE author.sex = @sex" ;
  • ph_style_question_mark : "WHERE author.sex = ?".
Voici le m�me exemple en utilisant les m�thodes C++ de la classe qx::QxSqlQuery (ou bien son alias qx_query) pour g�n�rer la requ�te automatiquement :

// Construit une requ�te pour r�cup�rer uniquement les 'author' de type 'female'
qx_query query;
query.where("author.sex").isEqualTo(author::female);

QList<author> list_of_female;
QSqlError daoError = qx::dao::fetch_by_query(query, list_of_female);
for (long l = 0; l < list_of_female.count(); l++)
{ /* traitement avec la collection issue de la base de donn�es */ }

Cette utilisation de la classe qx::QxSqlQuery pr�sente l'avantage de ne pas avoir � mapper les param�tres de la requ�te, tout en restant tr�s proche de l'�criture manuelle d'une requ�te SQL.
Les param�tres seront automatiquement inject�s en utilisant la syntaxe d�finie de mani�re globale par la m�thode : qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle().

Voici un exemple pr�sentant diff�rentes m�thodes disponibles avec la classe qx::QxSqlQuery (ou bien son alias qx_query) :

qx_query query;
query.where("sex").isEqualTo(author::female)
     .and_("age").isGreaterThan(38)
     .or_("last_name").isNotEqualTo("Dupont")
     .or_("first_name").like("Alfred")
     .and_OpenParenthesis("id").isLessThanOrEqualTo(999)
     .and_("birth_date").isBetween(date1, date2)
     .closeParenthesis()
     .or_("id").in(50, 999, 11, 23, 78945)
     .and_("is_deleted").isNotNull()
     .orderAsc("last_name", "first_name", "sex")
     .limit(50, 150);

Ce qui produira le code SQL suivant pour les bases de donn�es MySQL, PostgreSQL et SQLite (pour Oracle et SQLServer, le traitement de la m�thode limit() est diff�rent) :

WHERE sex = :sex_1_0 
AND age > :age_3_0 
OR last_name <> :last_name_5_0 
OR first_name LIKE :first_name_7_0 
AND ( id <= :id_10_0 AND birth_date BETWEEN :birth_date_12_0_1 AND :birth_date_12_0_2 ) 
OR id IN (:id_15_0_0, :id_15_0_1, :id_15_0_2, :id_15_0_3, :id_15_0_4) 
AND is_deleted IS NOT NULL 
ORDER BY last_name ASC, first_name ASC, sex ASC 
LIMIT :limit_rows_count_19_0 OFFSET :offset_start_row_19_0

Voici la liste des fonctions et m�thodes disponibles pour utiliser la classe qx::QxSqlQuery (ou bien son alias qx_query) :

// avec les fonctions du namespace qx::dao
qx::dao::count<T>()
qx::dao::fetch_by_query<T>()
qx::dao::update_by_query<T>()
qx::dao::delete_by_query<T>()
qx::dao::destroy_by_query<T>()
qx::dao::fetch_by_query_with_relation<T>()
qx::dao::fetch_by_query_with_all_relation<T>()
qx::dao::update_by_query_with_relation<T>()
qx::dao::update_by_query_with_all_relation<T>()
qx::dao::update_optimized_by_query<T>()

// avec la classe qx::QxSession
qx::QxSession::count<T>()
qx::QxSession::fetchByQuery<T>()
qx::QxSession::update<T>()
qx::QxSession::deleteByQuery<T>()
qx::QxSession::destroyByQuery<T>()

// avec la classe qx::QxRepository<T>
qx::QxRepository<T>::count()
qx::QxRepository<T>::fetchByQuery()
qx::QxRepository<T>::update()
qx::QxRepository<T>::deleteByQuery()
qx::QxRepository<T>::destroyByQuery()

Remarque : certaines de ces fonctions ont �galement deux autres param�tres optionnels :
  • const QStringList & columns : pour indiquer la liste des colonnes � r�cup�rer (par d�faut, toutes les colonnes sont r�cup�r�es) ;
  • const QStringList & relation : pour indiquer les jointures (one-to-one, one-to-many, many-to-one et many-to-many d�finies dans la fonction de mapping void qx::register_class<T>()) entre les tables de la base de donn�es (par d�faut, aucune relation).


Comment utiliser le cache (fonctions du namespace qx::cache) pour stocker tous types de donn�es ?

Le cache propos� par la biblioth�que QxOrm (module QxCache) est thread-safe et permet de stocker facilement n'importe quel type de donn�es.
Les fonctions pour acc�der au cache se trouvent dans le namespace qx::cache.
Le cache permet une optimisation du programme : il est possible par exemple de stocker des �l�ments issus d'une requ�te effectu�e en base de donn�es.

Chaque �l�ment stock� dans le cache est associ� � une cl� de type QString : cette cl� permet de retrouver rapidement un �l�ment du cache.
Si un nouvel �l�ment est stock� dans le cache avec une cl� qui existe d�j�, alors l'ancien �l�ment associ� � cette cl� est effac� automatiquement du cache.

Le cache de la biblioth�que QxOrm ne g�re pas la dur�e de vie des objets : il n'y a aucun delete effectu� par le cache.
C'est pourquoi il est fortement recommand� (mais ce n'est pas une obligation) de privil�gier le stockage de pointeurs intelligents : par exemple, boost::shared_ptr<T> pour la biblioth�que boost ou bien QSharedPointer<T> pour la biblioth�que Qt.

Le cache peut avoir un co�t relatif maximum pour �viter une utilisation de la m�moire trop importante : chaque �l�ment ins�r� dans le cache peut indiquer un co�t repr�sentant une estimation de sa taille m�moire (par exemple, le nombre d'�l�ments d'une collection).
Lorsque le co�t maximum du cache est atteint, les premiers �l�ments ins�r�s dans le cache sont supprim�s (en respectant l'ordre d'insertion dans le cache) jusqu'� ce que la limite du cache ne soit plus d�pass�e.

Il est possible d'associer � chaque �l�ment du cache une date-heure d'insertion.
Si aucune date-heure n'est renseign�e, alors la date-heure courante est prise en compte.
Ce m�canisme permet de v�rifier si un �l�ment stock� dans le cache n�cessite une mise � jour ou non.

Voici un exemple d'utilisation du cache de la biblioth�que QxOrm (fonctions du namespace qx::cache) :

// D�fini le co�t maximum du cache � 500
qx::cache::max_cost(500);

// R�cup�re une liste de 'author' de la base de donn�es
boost::shared_ptr< QList<author> > list_author;
QSqlError daoError = qx::dao::fetch_all(list_author);

// Ins�re la liste de 'author' dans le cache
qx::cache::set("list_author", list_author);

// R�cup�re une liste de 'blog' de la base de donn�es
QSharedPointer< std::vector<blog> > list_blog;
daoError = qx::dao::fetch_all(list_blog);

// Ins�re la liste de 'blog' dans le cache (co�t = nombre de 'blog')
qx::cache::set("list_blog", list_blog, list_blog.count());

// Pointeur vers un objet de type 'comment'
comment_ptr my_comment;
my_comment.reset(new comment(50));
daoError = qx::dao::fetch_by_id(my_comment);

// Ins�re le 'comment' dans le cache en pr�cisant une date-heure d'insertion
qx::cache::set("comment", my_comment, 1, my_comment->dateModif());

// R�cup�re la liste de 'blog' stock�e dans le cache
list_blog = qx::cache::get< QSharedPointer< std::vector<blog> > >("list_blog");

// R�cup�re la liste de 'blog' sans pr�ciser le type
qx_bool bGetOk = qx::cache::get("list_blog", list_blog);

// Supprime du cache la liste de 'author'
bool bRemoveOk = qx::cache::remove("list_author");

// Compte le nombre d'�l�ments du cache
long lCount = qx::cache::count();

// R�cup�re le co�t actuel des �l�ments stock�s dans le cache
long lCurrentCost = qx::cache::current_cost();

// V�rifie qu'un �l�ment associ� � la cl� "comment" existe dans le cache
bool bExist = qx::cache::exist("comment");

// R�cup�re le 'comment' stock� dans le cache avec sa date-heure d'insertion
QDateTime dt;
bGetOk = qx::cache::get("comment", my_comment, dt);

// Vide le cache
qx::cache::clear();



Comment g�n�rer le sch�ma SQL (cr�ation et mise � jour des tables) en fonction des classes persistantes C++ d�finies dans le contexte QxOrm ?

Il est recommand� d'utiliser l'application QxEntityEditor pour g�rer cette probl�matique.

La biblioth�que QxOrm ne fournit pas de m�canisme pour g�rer automatiquement la cr�ation et mise � jour des tables dans la base de donn�es.
En effet, la fonction qx::dao::create_table<T> doit �tre utilis�e uniquement pour cr�er des prototypes.
Il est fortement recommand� d'utiliser un outil sp�cifique � chaque SGBD pour cr�er et maintenir les tables de la base de donn�es (par exemple Navicat pour MySql, pgAdmin pour PostgreSQL, SQLite Manager pour SQLite, etc.).
De plus, un outil sp�cifique � chaque SGBD permet d'appliquer certaines optimisations (ajout d'index par exemple).

Cependant, il peut �tre int�ressant pour certaines applications de ne pas avoir � g�rer manuellement les tables de la base de donn�es.
Dans ce cas, il est possible de cr�er une fonction C++ pour parcourir la liste des classes persistantes enregistr�es dans le contexte QxOrm (en utilisant le moteur d'introspection de la biblioth�que) et ainsi cr�er un script SQL de g�n�ration et mise � jour des tables de la base de donn�es.

La biblioth�que QxOrm fournit une fonction C++ cr��e uniquement � titre d'exemple : elle peut donc servir de base de travail pour cr�er sa propre fonction de g�n�ration de script SQL.
Cette fonction se trouve dans le fichier ./src/QxRegister/QxClassX.cpp et se nomme QString qx::QxClassX::dumpSqlSchema().
Elle g�n�re un script SQL et le renvoie sous forme de QString : il est possible d'adapter cette fonction pour g�n�rer un fichier contenant le script SQL ou bien appliquer chaque instruction SQL directement au SGBD.

Voici un exemple d'impl�mentation propos� par dodobibi pour g�rer une base PostgreSQL : cet exemple g�re les �volutions futures de son application (ajout de colonnes dans une table existante, ajout d'index sur une colonne existante, etc.).
Au lancement de l'application, le num�ro de version est indiqu� de la fa�on suivante :

QApplication app(argc, argv);
app.setProperty("DomainVersion", 1); // Version increment�e � chaque compilation et diffusion de l'application

Une table de la base de donn�es permet de stocker le num�ro de version courant.
Une classe persistante C++ est mapp�e sur cette table de la fa�on suivante :

#ifndef _DATABASE_VERSION_H_
#define _DATABASE_VERSION_H_
 
class MY_DLL_EXPORT DatabaseVersion
{
public:
  QString name;
  long version;
};

QX_REGISTER_HPP_MY_APP(DatabaseVersion, qx::trait::no_base_class_defined, 0)

#endif // _DATABASE_VERSION_H_

#include "../include/precompiled.h"
#include "../include/DatabaseVersion.h"
#include <QxOrm_Impl.h>
 
QX_REGISTER_CPP_MY_APP(DatabaseVersion)

namespace qx {
template <> void register_class(QxClass<DatabaseVersion> & t)
{
  t.id(& DatabaseVersion::name, "name");
  t.data(& DatabaseVersion::version, "version");
}}

Avec la classe DatabaseVersion, il est possible de v�rifier si la version de la base de donn�es est � jour.
C'est le r�le de la fonction isDatabaseVersionOld() :

bool isDatabaseVersionOld()
{
  DatabaseVersion dbVersion;
  dbVersion.name = "MyAppName";
  QSqlError err = qx::dao::fetch_by_id(dbVersion);
  if (err.isValid()) { qAssert(false); return false; }
  return (dbVersion.version < qApp->property("DomainVersion").toInt());
}

Si au lancement de l'application, la fonction isDatabaseVersionOld() renvoie true, alors la mise � jour de la base de donn�es est effectu�e de la fa�on suivante :

void updateDatabaseVersion()
{
  try
  {
    int domainVersion = qApp->property("DomainVersion").toInt();

    // On se connecte avec un utilisateur de la base de donn�es qui a les droits de modifications du sch�ma
    QSqlDatabase db = qx::QxSqlDatabase::getSingleton()->getDatabaseCloned();
    db.setUserName("MyAdminLogin");
    db.setPassword("MyAdminPassword");

    // On s'assure que la session d�marre une transaction et l�ve une exception � la moindre erreur
    qx::QxSession session(db, true, true);

    // On "fetch" la version de la base de donn�es avec un verrou pour �viter les modifications concurrentes !
    // Si plusieurs utilisateurs lancent l'application en m�me temps et qu'une mise � jour
    // est n�cessaire, le premier fera la mise � jour, et les autres seront en attente
    DatabaseVersion dbVersion;
    session.fetchByQuery(qx_query("WHERE name='MyAppName' FOR UPDATE"), dbVersion);

    // Pour les autres utilisateurs, une fois le verrou lev�, on v�rifie si la mise � jour est toujours n�cessaire
    if (dbVersion.version >= domainVersion) { return; }

    // On ex�cute chaque instruction SQL avec la variable "query"
    QSqlQuery query(db);

    // On r�cup�re toutes les classes persistantes C++ enregistr�es dans le contexte QxOrm
    qx::QxCollection<QString, qx::IxClass *> * pAllClasses = qx::QxClassX::getAllClasses();
    if (! pAllClasses) { qAssert(false); return; }

    // on r�cup�re la liste des tables existantes dans la base (fonction de Qt)
    QStringList tables = db.tables();

    for (long k = 0; k < pAllClasses->count(); k++)
    {
      qx::IxClass * pClass = pAllClasses->getByIndex(k);
      if (! pClass) { continue; }

      // Filtre les classes non persistantes
      if (pClass->isKindOf("qx::service::IxParameter") || pClass->isKindOf("qx::service::IxService")) { continue; }

      // Filtre les classes � jour : si la version de pClass est <= � la version enregistr�e dans la base, la mise � jour n'est pas n�cessaire
      if (pClass->getVersion() <= dbVersion.version) { continue; }

      // On cr�e la table si elle n'existe pas, et on d�finit son propri�taire
      if (! tables.contains(pClass->getName()))
      {
        query.exec("CREATE TABLE " + pClass->getName() + " ( ) WITH (OIDS = FALSE);"
                   "ALTER TABLE " + pClass->getName() + " OWNER TO \"MyAdminLogin\";");
        session += query.lastError();
      }

      // On ajoute les colonnes � la table si elles n'existent pas
      qx::IxDataMemberX * pDataMemberX = pClass->getDataMemberX();
      for (long l = 0; (pDataMemberX && (l < pDataMemberX->count_WithDaoStrategy())); l++)
      {
        qx::IxDataMember * p = pDataMemberX->get_WithDaoStrategy(l);
        if (! p || (p->getVersion() <= dbVersion.version)) { continue; }

        query.exec("ALTER TABLE " + pClass->getName() + " ADD COLUMN " + p->getName() + " " + p->getSqlType() + ";");
        session += query.lastError();

        if (p->getIsPrimaryKey()) // PRIMARY KEY
        {
          query.exec("ALTER TABLE " + pClass->getName() + " ADD PRIMARY KEY (" + p->getName() + ");");
          session += query.lastError();
        }

        if (p->getAllPropertyBagKeys().contains("INDEX")) // INDEX
        {
          query.exec("CREATE INDEX " + pClass->getName() + "_" + p->getName() + "_idx" + 
                     " ON " + pClass->getName() + " USING " + p->getPropertyBag("INDEX").toString() + " (" + p->getName() + ");");
          session += query.lastError();
        }

        if (p->getNotNull()) // NOT NULL
        {
          query.exec("ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " SET NOT NULL;");
          session += query.lastError();
        }

        if (p->getAutoIncrement()) // AUTO INCREMENT
        {
          query.exec("CREATE SEQUENCE " + pClass->getName() + "_" + p->getName() + "_seq" + "; "
                     "ALTER TABLE " + pClass->getName() + "_" + p->getName() + "_seq" + " OWNER TO \"MyAdminLogin\"; "
                     "ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " " +
                     "SET DEFAULT nextval('" + pClass->getName() + "_" + p->getName() + "_seq" + "'::regclass);");
          session += query.lastError();
        }

        if (p->getDescription() != "") // DESCRIPTION
        {
          // $$ceci est un texte ne n�cessitant pas de caract�res d'�chappement dans postgres grace aux doubles dolars$$
          query.exec("COMMENT ON COLUMN " + pClass->getName() + "." + p->getName() + " IS $$" + p->getDescription() + "$$ ;");
          session += query.lastError();
        }
      }
    }

    // On enregistre la version courante de la base de donn�es
    dbVersion.version = domainVersion;
    session.save(dbVersion);

    // Fin du block "try" : la session est d�truite => commit ou rollback automatique
    // De plus, un commit ou rollback sur la transaction l�ve automatiquement le verrou pos� pr�c�demment
  }
  catch (const qx::dao::sql_error & err)
  {
    QSqlError sqlError = err.get();
    qDebug() << sqlError.databaseText();
    qDebug() << sqlError.driverText();
    qDebug() << sqlError.number();
    qDebug() << sqlError.type();
  }
}

Remarque : le code pr�c�dent (tout comme la fonction qx::QxClassX::dumpSqlSchema()) peut �tre modifi� pour s'adapter aux besoins sp�cifiques d'une application.
Par exemple, il pourrait �tre int�ressant de cr�er par d�faut une seconde table (en plus de la table DatabaseVersion) pour enregistrer la liste des classes persistantes enregistr�es dans le contexte QxOrm : ainsi, au lieu d'utiliser la fonction propos�e par Qt "db.tables()", il serait possible de r�cup�rer toutes les tables mapp�es sur des classes persistantes avec des informations suppl�mentaires (num�ro de version pour chaque table, nombre de colonnes enregistr�es dans le contexte QxOrm, description de chaque table, etc.).


Comment associer un type SQL � une classe C++ ?

Chaque base de donn�es propose des types SQL diff�rents pour stocker l'information.
La biblioth�que QxOrm propose une association par d�faut pour les classes C++ les plus fr�quemment utilis�es dans un programme.
Voici cette association par d�faut :

"bool" <-> "SMALLINT"
"qx_bool" <-> "SMALLINT"
"short" <-> "SMALLINT"
"int" <-> "INTEGER"
"long" <-> "INTEGER"
"long long" <-> "INTEGER"
"float" <-> "FLOAT"
"double" <-> "FLOAT"
"long double" <-> "FLOAT"
"unsigned short" <-> "SMALLINT"
"unsigned int" <-> "INTEGER"
"unsigned long" <-> "INTEGER"
"unsigned long long" <-> "INTEGER"
"std::string" <-> "TEXT"
"std::wstring" <-> "TEXT"
"QString" <-> "TEXT"
"QVariant" <-> "TEXT"
"QUuid" <-> "TEXT"
"QDate" <-> "DATE"
"QTime" <-> "TIME"
"QDateTime" <-> "TIMESTAMP"
"QByteArray" <-> "BLOB"
"qx::QxDateNeutral" <-> "TEXT"
"qx::QxTimeNeutral" <-> "TEXT"
"qx::QxDateTimeNeutral" <-> "TEXT"

Si le type SQL propos� par d�faut par la biblioth�que QxOrm ne correspond pas � la base de donn�es utilis�e, il peut facilement �tre modifi� (de mani�re globale � toute l'application) en utilisant la collection suivante :

QHash<QString, QString> * lstSqlType = qx::QxClassX::getAllSqlTypeByClassName();
lstSqlType->insert("QString", "VARCHAR(255)");
lstSqlType->insert("std::string", "VARCHAR(255)");
// etc.

Pour modifier le type SQL de mani�re sp�cifique pour une colonne d'une table de la base de donn�es, il faut d�finir le type SQL dans la fonction de mapping de QxOrm :

namespace qx {
template <> void register_class(QxClass<MyClass> & t)
{
  //...
  IxDataMember * p =  t.data(& MyClass::m_MyProperty, "my_property");
  p->setSqlType("VARCHAR(255)");
  //...
}}

Pour les classes non support�es par d�faut par la biblioth�que QxOrm (voir cette Question-R�ponse de la FAQ : Comment persister un type dont on ne poss�de pas le code source (classe provenant d'une biblioth�que tierce par exemple) ?), il est possible d'associer un type SQL par d�faut en utilisant la macro suivante (en dehors de tout namespace) :

QX_REGISTER_TRAIT_GET_SQL_TYPE(MyClass, "my_sql_type")



Comment utiliser le module QxValidator pour valider automatiquement les donn�es ?

Le module QxValidator de la biblioth�que QxOrm permet d'ajouter des contraintes sur les propri�t�s enregistr�es dans le contexte QxOrm.
Ces contraintes sont d�finies dans la m�thode de mapping : void qx::register_class<T>.
Si pour une instance de classe donn�e, au moins une contrainte n'est pas respect�e, alors l'instance est consid�r�e comme invalide : l'objet ne peut alors pas �tre sauvegard� en base de donn�es (INSERT ou UPDATE).

Il est �galement possible d'utiliser le module QxValidator pour valider les donn�es au niveau de la couche pr�sentation de l'application : si les donn�es saisies par un utilisateur ne sont pas valides, un message d'erreur peut �tre signal�, il n'est alors pas n�cessaire d'essayer d'enregistrer l'instance courante en base de donn�es.
Les r�gles de validation n'ont pas besoin d'�tre dupliqu�es : elles peuvent �tre utilis�es aussi bien par la couche pr�sentation que par la couche d'acc�s aux donn�es de l'application.

Voici la description de quelques classes du module QxValidator : Le module QxValidator g�re automatiquement la notion d'h�ritage de classe : si des contraintes sont d�finies au niveau de la classe de base, alors elles seront automatiquement v�rifi�es pour chaque validation d'une classe d�riv�e.

Voici un exemple d'utilisation du module QxValidator avec une classe 'person' :

* fichier 'person.h' :
#ifndef _CLASS_PERSON_H_
#define _CLASS_PERSON_H_
 
class person
{

public:

   enum sex { male, female, unknown };

   long        _id;
   QString     _firstName;
   QString     _lastName;
   QDateTime   _birthDate;
   sex         _sex;

   person() : _id(0), _sex(unknown) { ; }
   person(long id) : _id(id), _sex(unknown) { ; }
   virtual ~person() { ; }

private:

   void isValid(qx::QxInvalidValueX & invalidValues);

};

QX_REGISTER_HPP_MY_EXE(person, qx::trait::no_base_class_defined, 0)

#endif // _CLASS_PERSON_H_

* fichier 'person.cpp' :
#include "../include/precompiled.h"

#include "../include/person.h"
#include "../include/global_validator.h"

#include <QxOrm_Impl.h>

QX_REGISTER_CPP_MY_EXE(person)

namespace qx {
template <> void register_class(QxClass<person> & t)
{
   t.id(& person::_id, "id");

   t.data(& person::_firstName, "firstName");
   t.data(& person::_lastName, "lastName");
   t.data(& person::_birthDate, "birthDate");
   t.data(& person::_sex, "sex");

   QxValidatorX<person> * pAllValidator = t.getAllValidator();
   pAllValidator->add_NotEmpty("firstName");
   pAllValidator->add_NotEmpty("lastName", "a person must have a lastname");
   pAllValidator->add_CustomValidator(& person::isValid);
   pAllValidator->add_CustomValidator_QVariant(& validateFirstName, "firstName");
   pAllValidator->add_CustomValidator_DataType<QDateTime>(& validateDateTime, "birthDate");
}}

void person::isValid(qx::QxInvalidValueX & invalidValues)
{
   // Cette m�thode est appel�e automatiquement par le module 'QxValidator' :
   // - avant d'ins�rer ou mettre � jour une instance de type 'person' par les fonctions du namespace 'qx::dao' ;
   // - en utilisant la fonction 'qx::validate()' avec pour param�tre une instance de type 'person'.

   // L'enregistrement de la m�thode 'person::isValid()' est effectu� dans la fonction de mapping :
   // pAllValidator->add_CustomValidator(& person::isValid);

   // Dans cette m�thode, il est possible de v�rifier n'importe quelle valeur de l'instance courante
   // Si une propri�t� est non valide, il suffit d'ins�rer un �l�ment dans la collection 'invalidValues'

   // Remarque : cette m�thode est d�clar�e 'private' pour forcer l'utilisateur � utiliser la fonction 'qx::validate()'
   // Mais ce n'est pas une obligation : cette m�thode peut �tre d�clar�e 'public' ou 'protected'

   // Par exemple, si on souhaite v�rifier la propri�t� '_sex' d'une personne :
   if ((_sex != male) && (_sex != female))
   { invalidValues.insert("le sexe de la personne doit �tre d�fini : masculin ou f�minin"); }
}

* fichier 'global_validator.h' :
// Les fonctions suivantes ('validateFirstName()' et 'validateDateTime()') sont globales (non li�es � une classe)
// Elles peuvent ainsi �tre utilis�es par plusieurs classes pour valider une propri�t� (par exemple : valider la saisie d'une adresse IP).
// Ces fonctions seront appel�es automatiquement par le module 'QxValidator' :
// - avant d'ins�rer ou mettre � jour une instance de classe par les fonctions du namespace 'qx::dao' ;
// - en utilisant la fonction 'qx::validate()'.
 
void validateFirstName(const QVariant & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{
   // Ici, on peut tester la valeur d'une propri�t� (convertie en type QVariant)
   // Si la valeur est invalide, il suffit d'ins�rer un message � la collection 'invalidValues'

   // Par exemple, si la valeur ne doit jamais �tre �gale � "admin" :
   if (value.toString() == "admin")
   { invalidValues.insert("la valeur ne peut pas �tre �gale � 'admin'"); }
}

void validateDateTime(const QDateTime & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{
   // Ici, on peut tester la valeur d'une propri�t� (en conservant son vrai type, ici il s'agit de tester une date-heure de type 'QDateTime')
   // Si la valeur est invalide, il suffit d'ins�rer un message � la collection 'invalidValues'

   // Par exemple, si la date-heure doit forc�ment �tre renseign�e :
   if (! value.isValid())
   { invalidValues.insert("la date-heure doit �tre renseign�e et doit �tre valide"); }
}

* fichier 'main.cpp' :
person personValidate;
personValidate._lastName = "admin";
qx::QxInvalidValueX invalidValues = qx::validate(personValidate);
QString sInvalidValues = invalidValues.text();
qDebug("[QxOrm] test 'QxValidator' module :\n%s", qPrintable(sInvalidValues));

A l'ex�cution de ce bout de code, l'instance 'personValidate' est non valide : la collection 'invalidValues' contient quatre �l�ments :
- "la valeur de la propri�t� 'firstName' ne peut pas �tre vide" ;
- "le sexe de la personne doit �tre d�fini : masculin ou f�minin" ;
- "la valeur ne peut pas �tre �gale � 'admin'" ;
- "la date-heure doit �tre renseign�e et doit �tre valide".

Le module QxValidator fournit plusieurs validateurs pour effectuer des v�rifications basiques :
  • add_NotNull() : v�rifie que la valeur n'est pas nulle ;
  • add_NotEmpty() : v�rifie que la cha�ne de caract�res n'est pas vide ;
  • add_MinValue() : v�rifie que la valeur num�rique n'est pas inf�rieure au param�tre ;
  • add_MaxValue() : v�rifie que la valeur num�rique n'est pas sup�rieure au param�tre ;
  • add_Range() : v�rifie que la valeur num�rique est comprise entre les deux param�tres ;
  • add_MinDecimal() : v�rifie que la valeur d�cimale n'est pas inf�rieure au param�tre ;
  • add_MaxDecimal() : v�rifie que la valeur d�cimale n'est pas sup�rieure au param�tre ;
  • add_RangeDecimal() : v�rifie que la valeur d�cimale est comprise entre les deux param�tres ;
  • add_MinLength() : v�rifie que la cha�ne de caract�res a une taille minimale ;
  • add_MaxLength() : v�rifie que la cha�ne de caract�res ne d�passe pas un certain nombre de caract�res ;
  • add_Size() : v�rifie que la taille de la cha�ne de caract�res est comprise entre les deux param�tres ;
  • add_DatePast() : v�rifie que la date-heure est dans le pass� ;
  • add_DateFuture() : v�rifie que la date-heure est dans le futur ;
  • add_RegExp() : v�rifie que la cha�ne de caract�res est compatible avec l'expression r�guli�re pass�e en param�tre ;
  • add_EMail() : v�rifie que la cha�ne de caract�res correspond � un e-mail.
Comme dans l'exemple de la classe 'person', il est possible de d�finir �galement des validateurs personnalis�s : ce sont des fonctions ou m�thodes de classe qui seront appel�es automatiquement par le module QxValidator pour valider une propri�t� ou une instance de classe.
Il existe trois types de validateurs personnalis�s :
  • add_CustomValidator() : m�thode de classe, la signature de la m�thode doit �tre "void my_class::my_method(qx::QxInvalidValueX &)" ;
  • add_CustomValidator_QVariant() : fonction globale avec type QVariant (propri�t� convertie en QVariant), la signature de la fonction doit �tre "void my_validator(const QVariant &, const qx::IxValidator *, qx::QxInvalidValueX &)" ;
  • add_CustomValidator_DataType() : fonction globale avec le type r�el de la propri�t�, la signature de la fonction doit �tre "void my_validator(const T &, const qx::IxValidator *, qx::QxInvalidValueX &)" ;
Remarque : � chaque validateur peut �tre associ� un groupe (param�tre optionnel pour chaque m�thode add_XXX() de la classe qx::IxValidatorX).
Il est ainsi possible de cr�er des groupes de validation suivant le contexte d'ex�cution : par exemple, valider la saisie d'une personne sur une IHM A ne n�cessite peut-�tre pas les m�mes v�rifications que valider une personne sur une IHM B.
Pour ex�cuter la validation d'une instance pour un groupe donn� (par exemple "myGroup"), il faut appeler la fonction suivante : "qx::QxInvalidValueX invalidValues = qx::validate(personValidate, "myGroup");".

Autre remarque : le module QxValidator d�finit des messages par d�faut lorsqu'une contrainte n'est pas v�rifi�e.
Il est possible de red�finir ces messages par d�faut en modifiant la collection suivante : "QHash * lstMessage = QxClassX::getAllValidatorMessage(); ".
Par exemple : "lstMessage->insert("min_value", "la valeur '%NAME%' doit �tre inf�rieure ou �gale � '%CONSTRAINT%'");".
Les champs %NAME% et %CONSTRAINT% seront automatiquement remplac�s par les valeurs correspondantes.
Pour modifier le message pour un validateur donn� (et non de mani�re globale), il faut utiliser le param�tre optionnel disponible pour les m�thodes add_XXX() de la classe qx::IxValidatorX.


Comment utiliser l'interface qx::IxPersistable ?

L'interface qx::IxPersistable (ou classe abstraite) dispose uniquement de m�thodes virtuelles pures.
Elle permet d'avoir une classe de base commune pour appeler les fonctions de persistance sans conna�tre le type r�el de l'instance courante (notion de polymorphisme).
La biblioth�que QxOrm n'impose pas de travailler avec une classe de base pour enregistrer un type persistant dans le contexte QxOrm, cependant il est parfois utile de disposer d'une interface afin d'�crire des algorithmes g�n�riques.

La classe qx::IxPersistable met � disposition les m�thodes virtuelles suivantes (pour plus d'informations sur ces m�thodes, rendez-vous sur la documentation en ligne de la biblioth�que QxOrm) :

virtual long qxCount(const qx::QxSqlQuery & query = qx::QxSqlQuery(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchById(const QVariant & id = QVariant(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchAll(qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchByQuery(const qx::QxSqlQuery & query, qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxInsert(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxUpdate(const qx::QxSqlQuery & query = qx::QxSqlQuery(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxSave(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteAll(QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyAll(QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL);
virtual qx_bool qxExist(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual qx::QxInvalidValueX qxValidate(const QStringList & groups = QStringList());
virtual qx::IxPersistableCollection_ptr qxNewPersistableCollection() const;
virtual qx::IxClass * qxClass() const;

Par exemple, � partir d'une liste de pointeurs de type qx::IxPersistable, il est possible d'enregistrer les �l�ments dans plusieurs tables diff�rentes de la base de donn�es de la fa�on suivante :

QList<qx::IxPersistable *> lst = ...;
foreach(qx::IxPersistable * p, lst)
{
   QSqlError daoError = p->qxSave();
   if (daoError.isValid()) { /* an error occured */ }
   // etc...
}

Pour impl�menter l'interface qx::IxPersistable, il faut :
  • faire h�riter la classe persistante du type qx::IxPersistable ;
  • dans la d�finition de la classe (myClass.h par exemple), ajouter la macro QX_PERSISTABLE_HPP(myClass) ;
  • dans l'impl�mentation de la classe (myClass.cpp par exemple), ajouter la macro QX_PERSISTABLE_CPP(myClass).
Par exemple, impl�menter l'interface qx::IxPersistable pour la classe author du tutoriel qxBlog revient � �crire (les modifications par rapport au code du tutoriel apparaissent en gras) :

#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_

class blog;

class QX_BLOG_DLL_EXPORT author : public qx::IxPersistable
{
   QX_PERSISTABLE_HPP(author)
public:
// -- 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
   QString     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;
};

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

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

#endif // _QX_BLOG_AUTHOR_H_

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

#include "../include/author.h"
#include "../include/blog.h"

#include <QxOrm_Impl.h>

QX_REGISTER_CPP_QX_BLOG(author)
QX_PERSISTABLE_CPP(author)

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

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

   t.relationOneToMany(& author::m_blogX, "list_blog", "author_id");

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

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

Remarque : le projet de test ./test/qxDllSample/dll1/ met � disposition une sorte de 'super classe de base' : la classe qx::QxPersistable impl�mente l'interface qx::IxPersistable et h�rite de QObject.
Le m�canisme SIGNAL-SLOT de Qt peut donc �tre utilis� avec cette classe, ce qui peut �tre int�ressant par exemple pour la notion de d�clencheurs (ou trigger).
La classe qx::QxPersistable met �galement � disposition des m�thodes virtuelles qu'il est possible de surcharger pour g�rer notamment la notion de validation des donn�es avec le module QxValidator.
La classe qx::QxPersistable ne fait pas partie de la distribution de QxOrm, mais il est possible de la copier-coller dans un projet afin de profiter de ses fonctionnalit�s :


Comment utiliser le moteur de relations pour r�cup�rer des donn�es associ�es � plusieurs tables ?

La biblioth�que QxOrm supporte quatre types de relations pour lier les classes C++ enregistr�es dans le contexte QxOrm : one-to-one, one-to-many, many-to-one et many-to-many.
Pour plus de d�tails sur la d�finition de ces relations, il est conseill� de lire le tutoriel qxBlog.
Nous allons d�tailler dans cette Q&R les diff�rentes m�thodes de r�cup�ration des donn�es (module QxDao, fonctions du namespace qx::dao) : Le premier param�tre des fonctions fetch_by_id_with_relation, fetch_all_with_relation et fetch_by_query_with_relation correspond � la liste des relations � requ�ter.
Cette liste de relations peut contenir les �l�ments suivants :
  • identifiant d'une relation : chaque relation poss�de une cl� d�finie au niveau de la fonction de param�trage qx::register_class<T> ;
  • le mot-cl� "*" signifie "r�cup�rer toutes les relations d�finies dans la fonction de param�trage qx::register_class<T> sur un niveau" ;
  • le mot-cl� "->" signifie jointure de type "LEFT OUTER JOIN" (jointure par d�faut de la biblioth�que QxOrm) ;
  • le mot-cl� ">>" signifie jointure de type "INNER JOIN" entre deux tables.
Remarque : en utilisant le mot-cl� "*" pour indiquer "toutes les relations sur un niveau", les appels suivants sont �quivalents :
  • qx::dao::fetch_by_id_with_relation("*", ...) == qx::dao::fetch_by_id_with_all_relation(...) ;
  • qx::dao::fetch_by_query_with_relation("*", ...) == qx::dao::fetch_by_query_with_all_relation(...) ;
  • qx::dao::fetch_all_with_relation("*", ...) == qx::dao::fetch_all_with_all_relation(...).

Exemple : � partir du tutoriel qxBlog, il est possible de r�cup�rer les donn�es suivantes avec une seule requ�te :

1- r�cup�rer un blog et son author ;
2- pour l'author valoris�, r�cup�rer tous les blog qu'il a �crit ;
3- pour chaque blog que l'author a �crit, r�cup�rer tous les comment associ�s.

blog_ptr my_blog = blog_ptr(new blog(10));
QSqlError daoError = qx::dao::fetch_by_id_with_relation("author_id->list_blog->list_comment", my_blog);

Ce qui g�n�re la requ�te SQL suivante :
SELECT blog.blog_id AS blog_blog_id_0, blog.blog_text AS blog_blog_text_0, blog.date_creation AS blog_date_creation_0, blog.author_id AS blog_author_id_0, 
       author_1.author_id AS author_1_author_id_0, author_1.name AS author_1_name_0, author_1.birthdate AS author_1_birthdate_0, author_1.sex AS author_1_sex_0, 
       blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0, 
       comment_4.comment_id AS comment_4_comment_id_0, comment_4.blog_id AS comment_4_blog_id_0, comment_4.comment_text AS comment_4_comment_text_0, comment_4.date_creation AS comment_4_date_creation_0 
FROM blog 
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id 
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id 
LEFT OUTER JOIN comment comment_4 ON comment_4.blog_id = blog_2.blog_id 
WHERE blog.blog_id = :blog_id


Autre exemple : il est �galement possible de cr�er une liste de relations � r�cup�rer, comme ceci par exemple :

blog_ptr my_blog = blog_ptr(new blog(10));
QStringList relation;
relation << "author_id->list_blog->list_comment";
relation << "author_id->list_blog->list_category";
relation << "list_comment";
relation << "list_category";
QSqlError daoError = qx::dao::fetch_by_id_with_relation(relation, my_blog);

Ce qui g�n�re la requ�te SQL suivante :
SELECT blog.blog_id AS blog_blog_id_0, blog.blog_text AS blog_blog_text_0, blog.date_creation AS blog_date_creation_0, blog.author_id AS blog_author_id_0, 
       author_1.author_id AS author_1_author_id_0, author_1.name AS author_1_name_0, author_1.birthdate AS author_1_birthdate_0, author_1.sex AS author_1_sex_0, 
       blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0, 
       category_5.category_id AS category_5_category_id_0, category_5.name AS category_5_name_0, category_5.description AS category_5_description_0, 
       comment_6.comment_id AS comment_6_comment_id_0, comment_6.blog_id AS comment_6_blog_id_0, comment_6.comment_text AS comment_6_comment_text_0, comment_6.date_creation AS comment_6_date_creation_0, 
       category_7.category_id AS category_7_category_id_0, category_7.name AS category_7_name_0, category_7.description AS category_7_description_0 
FROM blog 
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id 
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id 
LEFT OUTER JOIN category_blog category_blog_5 ON blog_2.blog_id = category_blog_5.blog_id 
LEFT OUTER JOIN category category_5 ON category_blog_5.category_id = category_5.category_id 
LEFT OUTER JOIN comment comment_6 ON comment_6.blog_id = blog.blog_id 
LEFT OUTER JOIN category_blog category_blog_7 ON blog.blog_id = category_blog_7.blog_id 
LEFT OUTER JOIN category category_7 ON category_blog_7.category_id = category_7.category_id 
WHERE blog.blog_id = :blog_id


Autre exemple : pour r�cup�rer toutes les relations pour un niveau donn�, il faut utiliser le mot-cl� "*".
Pour r�cup�rer toutes les donn�es de toutes les relations sur trois niveaux, il faut �crire :

blog_ptr my_blog = blog_ptr(new blog(10));
QSqlError daoError = qx::dao::fetch_by_id_with_relation("*->*->*", my_blog);

Ce qui g�n�re la requ�te SQL suivante :
SELECT blog.blog_id AS blog_blog_id_0, blog.blog_text AS blog_blog_text_0, blog.date_creation AS blog_date_creation_0, blog.author_id AS blog_author_id_0, 
       author_1.author_id AS author_1_author_id_0, author_1.name AS author_1_name_0, author_1.birthdate AS author_1_birthdate_0, author_1.sex AS author_1_sex_0, 
       blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0, blog_2.author_id AS blog_2_author_id_0_2, 
       author_3.author_id AS author_3_author_id_0, author_3.name AS author_3_name_0, author_3.birthdate AS author_3_birthdate_0, author_3.sex AS author_3_sex_0, 
       comment_4.comment_id AS comment_4_comment_id_0, comment_4.blog_id AS comment_4_blog_id_0, comment_4.comment_text AS comment_4_comment_text_0, comment_4.date_creation AS comment_4_date_creation_0, 
       category_5.category_id AS category_5_category_id_0, category_5.name AS category_5_name_0, category_5.description AS category_5_description_0, 
       comment_6.comment_id AS comment_6_comment_id_0, comment_6.blog_id AS comment_6_blog_id_0, comment_6.comment_text AS comment_6_comment_text_0, comment_6.date_creation AS comment_6_date_creation_0, comment_6.blog_id AS comment_6_blog_id_0_6, 
       blog_7.blog_id AS blog_7_blog_id_0, blog_7.blog_text AS blog_7_blog_text_0, blog_7.date_creation AS blog_7_date_creation_0, blog_7.author_id AS blog_7_author_id_0_7, 
       author_8.author_id AS author_8_author_id_0, author_8.name AS author_8_name_0, author_8.birthdate AS author_8_birthdate_0, author_8.sex AS author_8_sex_0, 
       comment_9.comment_id AS comment_9_comment_id_0, comment_9.blog_id AS comment_9_blog_id_0, comment_9.comment_text AS comment_9_comment_text_0, comment_9.date_creation AS comment_9_date_creation_0, 
       category_10.category_id AS category_10_category_id_0, category_10.name AS category_10_name_0, category_10.description AS category_10_description_0, 
       category_11.category_id AS category_11_category_id_0, category_11.name AS category_11_name_0, category_11.description AS category_11_description_0, 
       blog_12.blog_id AS blog_12_blog_id_0, blog_12.blog_text AS blog_12_blog_text_0, blog_12.date_creation AS blog_12_date_creation_0, blog_12.author_id AS blog_12_author_id_0_12, 
       author_13.author_id AS author_13_author_id_0, author_13.name AS author_13_name_0, author_13.birthdate AS author_13_birthdate_0, author_13.sex AS author_13_sex_0, 
       comment_14.comment_id AS comment_14_comment_id_0, comment_14.blog_id AS comment_14_blog_id_0, comment_14.comment_text AS comment_14_comment_text_0, comment_14.date_creation AS comment_14_date_creation_0, 
       category_15.category_id AS category_15_category_id_0, category_15.name AS category_15_name_0, category_15.description AS category_15_description_0 
FROM blog 
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id 
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id 
LEFT OUTER JOIN author author_3 ON author_3.author_id = blog_2.author_id 
LEFT OUTER JOIN comment comment_4 ON comment_4.blog_id = blog_2.blog_id 
LEFT OUTER JOIN category_blog category_blog_5 ON blog_2.blog_id = category_blog_5.blog_id 
LEFT OUTER JOIN category category_5 ON category_blog_5.category_id = category_5.category_id 
LEFT OUTER JOIN comment comment_6 ON comment_6.blog_id = blog.blog_id 
LEFT OUTER JOIN blog blog_7 ON blog_7.blog_id = comment_6.blog_id 
LEFT OUTER JOIN author author_8 ON author_8.author_id = blog_7.author_id 
LEFT OUTER JOIN comment comment_9 ON comment_9.blog_id = blog_7.blog_id 
LEFT OUTER JOIN category_blog category_blog_10 ON blog_7.blog_id = category_blog_10.blog_id 
LEFT OUTER JOIN category category_10 ON category_blog_10.category_id = category_10.category_id 
LEFT OUTER JOIN category_blog category_blog_11 ON blog.blog_id = category_blog_11.blog_id 
LEFT OUTER JOIN category category_11 ON category_blog_11.category_id = category_11.category_id 
LEFT OUTER JOIN category_blog category_blog_12 ON category_11.category_id = category_blog_12.category_id 
LEFT OUTER JOIN blog blog_12 ON category_blog_12.blog_id = blog_12.blog_id 
LEFT OUTER JOIN author author_13 ON author_13.author_id = blog_12.author_id 
LEFT OUTER JOIN comment comment_14 ON comment_14.blog_id = blog_12.blog_id 
LEFT OUTER JOIN category_blog category_blog_15 ON blog_12.blog_id = category_blog_15.blog_id 
LEFT OUTER JOIN category category_15 ON category_blog_15.category_id = category_15.category_id 
WHERE blog.blog_id = :blog_id



Comment appeler une proc�dure stock�e ou une requ�te SQL personnalis�e ?

La biblioth�que QxOrm fournit deux fonctions pour appeler une proc�dure stock�e ou une requ�te SQL personnalis�e : Le premier param�tre de ces deux fonctions, de type qx::QxSqlQuery (ou qx_query), correspond � la proc�dure stock�e ou � la requ�te SQL personnalis�e.
Pour plus d'informations sur la classe qx::QxSqlQuery, rendez-vous sur cette Q&R de la FAQ de QxOrm : Comment construire une requ�te pour interroger la base de donn�es sans �crire de SQL avec la classe qx::QxSqlQuery ?

La fonction qx::dao::execute_query<T>() est une fonction template : le type T doit �tre enregistr� dans le contexte QxOrm (fonction qx::register_class<T>).
Toutes les donn�es renvoy�es par la proc�dure stock�e ou la requ�te SQL personnalis�e qui pourront �tre associ�es aux membres des classes C++ (de type T) seront valoris�es automatiquement.
Une recherche automatique est effectu�e sur le nom des champs associ�s aux donn�es.
Voici un exemple d'utilisation (disponible dans le projet qxBlog du package QxOrm) :

// Call a custom SQL query or a stored procedure and fetch automatically properties (with a collection of items)
qx_query testStoredProcBis("SELECT * FROM author");
daoError = qx::dao::execute_query(testStoredProcBis, authorX);
qAssert(! daoError.isValid()); qAssert(authorX.count() > 0);
qx::dump(authorX);


La fonction qx::dao::call_query() n'est pas une fonction template : les r�sultats de la requ�te doivent �tre parcourus manuellement sur la classe qx::QxSqlQuery (ou qx_query).
Pour r�cup�rer un param�tre de sortie (qui doit �tre pass� � la requ�te en tant que QSql::Out ou QSql::InOut), il suffit d'utiliser la m�thode : QVariant qx::QxSqlQuery::boundValue(const QString & sKey) const;.

Pour parcourir la liste des r�sultats de la requ�te, il faut utiliser les m�thodes suivantes :
* long qx::QxSqlQuery::getSqlResultRowCount() const;
* long qx::QxSqlQuery::getSqlResultColumnCount() const;
* QVariant qx::QxSqlQuery::getSqlResultAt(long row, long column) const;
* QVariant qx::QxSqlQuery::getSqlResultAt(long row, const QString & column) const;
* QVector qx::QxSqlQuery::getSqlResultAllColumns() const;
* void qx::QxSqlQuery::dumpSqlResult();

Voici un exemple d'utilisation avec la fonction qx::dao::call_query() :

qx_query query("CALL MyStoredProc(:param1, :param2)");
query.bind(":param1", "myValue1");
query.bind(":param2", 5024, QSql::InOut);
QSqlError daoError = qx::dao::call_query(query);
QVariant vNewValue = query.boundValue(":param2");
query.dumpSqlResult();



Comment utiliser la classe qx::QxDaoAsync pour appeler des requ�tes de mani�re asynchrone (multi-thread) ?

Il peut �tre parfois int�ressant d'ex�cuter certaines requ�tes � la base de donn�es de mani�re asynchrone (multi-thread), par exemple pour �viter de bloquer une IHM si une requ�te est trop longue � s'ex�cuter.
Pour simplifier les requ�tes asynchrones, la biblioth�que QxOrm fournit la classe qx::QxDaoAsync.
Cette classe ex�cute une requ�te dans un thread d�di� et renvoie un SIGNAL queryFinished() lorsque la requ�te est termin�e.
Pour utiliser la classe qx::QxDaoAsync, il suffit de :
  • v�rifier que la requ�te fait appel � une classe qui impl�mente l'interface qx::IxPersistable ;
  • cr�er une instance de type qx::QxDaoAsync (par exemple, une propri�t� membre d'une classe d�rivant du type QWidget) ;
  • connecter un SLOT au SIGNAL qx::QxDaoAsync::queryFinished() (par exemple, un SLOT d�fini dans une classe d�rivant du type QWidget) ;
  • ex�cuter une requ�te � la base de donn�es en utilisant l'une des m�thodes commen�ant par async : qx::QxDaoAsync::asyncXXXX().
Voici un exemple d'utilisation avec une classe nomm�e MyWidget :

class MyWidget : public QWidget
{ Q_OBJECT

   //...
   qx::QxDaoAsync m_daoAsync;
   //...
Q_SLOTS:
   void onQueryFinished(const QSqlError & daoError, qx::dao::detail::QxDaoAsyncParams_ptr pDaoParams);
   //...

};

Et voici l'impl�mentation de la classe MyWidget :

MyWidget::MyWidget() : QObject()
{
   //...
   QObject::connect((& m_daoAsync), SIGNAL(queryFinished(const QSqlError &, qx::dao::detail::QxDaoAsyncParams_ptr)), 
                    this, SLOT(onQueryFinished(const QSqlError &, qx::dao::detail::QxDaoAsyncParams_ptr)));
   //...
}

void MyWidget::onQueryFinished(const QSqlError & daoError, qx::dao::detail::QxDaoAsyncParams_ptr pDaoParams)
{
   if (! pDaoParams) { return; }
   qx::QxSqlQuery query = pDaoParams->query;
   if (! daoError.isValid()) { ; }
   // If the async query is associated to a simple object, just use 'pDaoParams->pInstance' method
   qx::IxPersistable_ptr ptr = pDaoParams->pInstance;
   // If the async query is associated to a list of objects, just use 'pDaoParams->pListOfInstances' method
   qx::IxPersistableCollection_ptr lst = pDaoParams->pListOfInstances;
   //...
}



Comment utiliser le module QxModelView pour travailler avec le moteur model/view de Qt (Qt widgets et vues QML) ?

Le module QxModelView permet d'utiliser le moteur model/view de Qt avec toutes les classes enregistr�es dans le contexte QxOrm :
  • Qt widgets : utilisation de QTableView ou QListView par exemple pour afficher/modifier le contenu d'une table de base de donn�es ;
  • QML : toute propri�t� enregistr�e dans le contexte QxOrm est accessible en QML : le module QxModelView permet ainsi de faciliter l'int�raction entre QML et les bases de donn�es.
L'interface qx::IxModel propose une base commune pour tous les mod�les li�s aux classes persistantes d�clar�es dans le contexte QxOrm. Les m�thodes de cette classe pr�fix�es par 'qx' appellent les fonctions du namespace 'qx::dao' et communiquent donc directement avec la base de donn�es.

Le projet de test qxBlogModelView pr�sent dans le dossier ./test/ du package QxOrm montre comment cr�er rapidement un mod�le et l'associer au moteur model/view de Qt (d'abord dans un widget Qt, puis dans une vue QML).

1- Exemple de cr�ation d'un mod�le pour afficher/modifier les donn�es de la table 'author' (voir le tutoriel qxBlog pour la d�finition de la classe 'author') dans un QTableView :

// Create a model and fetch all data from database
qx::IxModel * pModel = new qx::QxModel<author>();
pModel->qxFetchAll();

// Associate the model to a QTableView and display it
QTableView tableView;
tableView.setModel(pModel);
tableView.show();

Ce qui donne le r�sultat suivant � l'ex�cution :

qx_model_view_01


2- Voici un autre exemple en QML (en Qt5, le module QxModelView �tant �galement compatible avec Qt4) :

// Create a model and fetch all data from database
qx::IxModel * pModel = new qx::QxModel<author>();
pModel->qxFetchAll();

// Associate the model to a QML view and display it
QQuickView qmlView;
qmlView.rootContext()->setContextProperty("myModel", pModel);
qmlView.setSource(QUrl("qrc:/documents/main.qml"));
qmlView.show();

Et voici le contenu du fichier 'main.qml' :

import QtQuick 2.1
import QtQuick.Controls 1.0

Item {
   width: 400
   height: 300
   Row {
      height: 20
      spacing: 20
      Button {
         text: "Clear"
         onClicked: myModel.clear()
      }
      Button {
         text: "Fetch All"
         onClicked: myModel.qxFetchAll_()
      }
      Button {
         text: "Save"
         onClicked: myModel.qxSave_()
      }
   }
   ListView {
      y: 30
      height: 270
      model: myModel
      delegate: Row {
         height: 20
         spacing: 10
         Text { text: "id: " + author_id }
         TextField {
            text: name
            onTextChanged: name = text
         }
      }
   }
}

Ce qui donne le r�sultat suivant � l'ex�cution :

qx_model_view_02

Comme on peut le constater dans le fichier 'main.qml', les propri�t�s 'author_id' et 'name' du mod�le 'author' (variable myModel) sont accessibles automatiquement en lecture/�criture (car elles ont �t� enregistr�es dans le contexte QxOrm).
De plus, l'interface qx::IxModel propose une liste de m�thodes accessibles en QML (utilisation de Q_INVOKABLE) pour communiquer directement avec la base de donn�es : ainsi, le bouton 'Save' de l'�cran ci-dessus enregistre le mod�le en base de donn�es depuis QML.

Remarque : un plugin de QxEntityEditor permet de g�n�rer automatiquement le code des mod�les pour la gestion des relations. Il est ainsi possible de travailler avec des mod�les imbriqu�s.




QxOrm � 2011-202XDL Teamty - ic-east.com