Comment va votre XPath

Ce billet fait partie d’une série d’articles en provenance du 🇨🇦 sur les bonnes pratiques Mendix et dédié uniquement aux francophones 😜. Vous trouverez en fin de page des liens vers les autres billets.


Pour débuter cet article, voyons ce qu’est XPath (XML Path Language). Il s’agit d’une convention d’écriture (ou sémantique) qui permet de naviguer au travers d’une structure arborescente (qu’on appelle noeuds) d’un document XML afin d’en sélectionner ses composantes : les éléments et leurs attributs. Sa norme est développée par le WWW Consortium depuis 1999 et, en date du 21 mars 2017, nous en sommes à la version 3.1. Cette syntaxe, qui est relativement facile d’utilisation, a été adoptée par les développeurs afin d’interroger plus simplement les sources de données.

Mendix intègre sa propre implémentation de XPath pour interroger les bases de données. Ceci signifie que vous ne retrouverez pas toute la palette des fonctionnalités présentées par la norme et que dans certains cas, vous devrez jouer d’astuces pour contourner certaines de ses limitations. Cependant, l’implémentation actuelle qui est riche, ne devrait pas vous arrêter dans vos réalisations…

XPath sous Mendix

XPath sous Mendix repose sur une structure hiérarchisée d’expressions autorisant la sélection d’objets (entité ou table) et de leurs attributs (champs) ou associations (jointures).

On utilise cette syntaxe dans plusieurs situations :

  • dans les pages, pour contraindre un contexte, comme une grille de données (data grid) ou bien la liste d’éléments pouvant être sélectionnés (reference selector),
  • dans des microflux, lors d’une requête sur des données,
  • et enfin au niveau de la sécurité afin d’en contraindre l’accès (access rights).

Dans la suite de cet article, vous allez retrouver l’ensemble des fonctions et syntaxes disponibles dans la version actuelle de Mendix Studio Pro. Mais avant, je souhaite vous présenter deux choses : des astuces utilisées pour optimiser vos requêtes XPath et certaines limitations actuelles dans l’implémentation du langage. Bien entendu, je vous invite aussi à partager dans les commentaires vos propres trouvailles.

Optimisation de vos requêtes XPath

Voici 6 astuces pour optimiser vos requêtes :

  1. Choisir l’ordre des contraintes : lorsqu’une requête est exécutée au niveau de votre base de données, les contraintes définies dans votre XPath sont traitées en respectant l’ordre de votre déclaration. Sachant cela, il faut garder à l’esprit de toujours écrire ses contraintes de manière à avoir celle qui restreint le plus le nombre de résultats en premier. Par exemple, vous pouvez imaginer qu’une contrainte sur une valeur booléenne vous retournera 50% de vos données alors qu’une contrainte sur une date peut réduire considérablement le nombre de résultats. Dans ce cas, l’ordre optimal d’écriture de ces deux contraintes sera naturellement de privilégier celle sur la date avant celle sur la valeur booléenne.
  2. Réduire l’utilisation de chemins longs : si vous utilisez des chemins longs dans votre requête XPath avec différents critères, pensez à fusionner autant que possible ceux-ci. Par exemple, si vous devez appliquer deux critères sur une même entité, regroupez les simplement dans une seule. Par exemple, ceci : [Jardin.Jardin_Proprietaire/Jardin.Proprietaire/Nom=’Alfred’ or Jardin.Jardin_Proprietaire/Jardin.Proprietaire/Age>20] peut être écrit comme cela [Jardin.Jardin_Proprietaire/Jardin.Proprietaire[Nom=’Alfred’ or Age>20]]
  3. Utiliser le cache de votre base de données : dans l’éventualité ou votre application utilise de nombreuses fois les mêmes contraintes de recherche, il est utile de conserver le même ordre de celles-ci car le moteur de base de données tentera d’obtenir ses résultats de son propre cache avant d’interroger le répertoire de données lui-même. Bien entendu, si la même requête est utilisée, je vous suggère de l’isoler dans un sous-microflux.
  4. Minimiser l’utilisation du OR et du not() : autant que faire se peut, il est préférable de ne pas utiliser des conditions comme OR et not(). Exécuter deux simples requêtes et combiner leurs résultats par une UNION est plus rapide que d’exécuter une seule requête.
  5. Simplifier la syntaxe sur la condition true : lorsque vous écrivez une contrainte utilisant la validation sur un attribut booléen, vous n’avez pas besoin d’indiquer explicitement votre test car, par défaut, une telle condition est évaluée avec la valeur true. Exemple : //Jardin.Legume[Hivernal=true()] peut s’écrire //Jardin.Legume[Hivernal]. Attention tout de même, si vous utilisez un attribut d’une entité nulle, sa valeur va être empty et sera ignorée lors de l’analyse des contraintes XPath. Dans mon exemple ci-dessus, si l’entité ‘Legume’ n’existait pas, la contrainte sur Hivernal serait ignorée. Pour le coup, tous les légumes seraient retournés par la requête.
  6. Utiliser les indexes : vous devez faire très attention à l’ordre dans lequel vous allez écrire vos contraintes. Celles-ci doivent respecter l’ordre de vos index sur vos tables de données si vous souhaitez que votre requête soit optimisée (c’est-à-dire utilise les indexes en question).

Limitations actuelles

Si vous utilisez couramment la syntaxe XPath, peut-être avez-vous rencontré certaines limitations. N’hésitez pas à nous les partager en commentaires de cet article et d’en faire profiter tout le monde, surtout si vous avez des suggestions pour contourner le problème rencontré.

  • Une sensibilité à la casse : attention, lorsque vous exécutez une recherche et que votre base de données a été configurée avec une sensibilité à la casse, vous risquez de ne pas trouver certains résultats.
  • Pas de limites : il n’est pas possible de limiter le nombre d’objets retournés. Par exemple, une syntaxe de type LIMIT n’existe pas.
  • Pas de recherche dans une liste : si vous souhaitez rechercher des objets dont une propriété est issue d’une liste vous ne bénéficiez pas de la syntaxe : IN(…). Vous devrez convertir cette liste en OR.

Fonctions et syntaxe

Voici une liste des fonctions et syntaxes que nous pouvons retrouver dans la documentation de Mendix. N’hésitez pas à vous y reporter car, au fil des versions de la plateforme, de nouvelles options sont ajoutées. Ceci a été le cas dernièrement avec l’ajout de nouvelles fonctions couvrant la manipulation des dates.

La syntaxe de base utilise les caractères suivants :

SyntaxeDescriptionExemple
//Racine à partir de laquelle les entités seront extraites. Elle est toujours suivie du nom du module dans lequel est stockée l’information.//Jardin.Legume
(tous les légumes du jardin).
/Séparateur relationnel entre les entités.//Jardin.Legume/Culture_Legume/Culture

Relation entre un légume du jardin et sa culture.
.Séparateur entre un module et une entité//Jardin.Legume
Le légume est associé au module Jardin.
[ ]Crochets de groupement pour l’écriture de conditions. Chaque groupement est lié avec son précédent avec une condition (ou clause) ET (AND).//Jardin.Legume[Couleur=’Verte’]
On veut les légumes du jardin qui ont une couleur verte.
( )La parenthèse permet de regrouper des conditions. L’exécution des conditions sera alors analysée par le contenu des parenthèses.//Jardin.Legume[(Couleur=’Verte’ or Couleur=’Jaune’) and TypeDeSol=’Terre’]
On veut les légumes du jardin qui sont de couleur soit verte ou jaune et dont le type de sol est de la terre.
‘ ’La simple quote permet d’encadrer les chaînes de caractères. //Jardin.Legume[Couleur=’Verte’]

Les opérateurs :

OpérateurDescriptionExempleValeur retournée
+Addition2 + 46
Soustraction2 – 64
*Multiplication2 * 48
divDivision8 div 24
=Égal àprix=10.50true (vrai) si le prix est de 10.50
false (faux) si le prix est de 10.00
!=Différent deprix != 10.50true (vrai) si le prix est de 11.00
false (faux) si le prix est égal 10.50.
Strictement inférieur à ou strictement plus petit queprix < 10.50true (vrai) si le prix est de 9.45
false (faux) si le prix est de 11.00
<=Inférieur ou égal ou plus petit ou égal àprix <= 10.50true (vrai) si le prix est de 9.00
true (vrai) si le prix est de 10.50
false (faux) si le prix est de 10.75
Strictement supérieur à ou strictement plus grand que.prix > 10.50true (vrai) si le prix = 11.55
false (faux) si le prix est égal à 10.50
false (faux) si le prix est de 11.00
>=Supérieur ou égal à ou plus grand ou égal àprix  >= 10.50true (vrai) si le prix = 11.55
true (vrai) si le prix est égal à 10.50
false (faux) si le prix est de 11.00
orOu bien (alternative)prix=10.00 or prix=20.00true (vrai) si le prix = 10.00
true (vrai) si le prix = 20.00
false (faux) si le prix = 15.00
andEtprix = 10 and quantite=20true (vrai) si le prix =10 et la quantité=20.
false (faux) si le prix=20 et la quantité=20.

Les fonctions disponibles pour définir les contraintes de sélection :

ÉlémentDescriptionExemple
idVérification si l’identifiant de l’objet manipulé est égale à un autre identifiant.//Jardin.Legume[id=$CurrentLegume] (1)
emptyValide que l’attribut n’est pas vide.//Jardin.Legume[Couleur!=empty]
containsVérifie qu’une sous-chaîne de caractères est contenue dans une autre.[contains(Couleur, ‘Ver’)]
start-withValide que l’attribut de l’objet débute bien par la chaîne de caractères recherchée.[start-with(Couleur, ‘Ver’)]
ends-withValide que l’attribut de l’objet se termine bien par la chaîne de caractères recherchée.[ends-with(Couleur, ‘rt’)]
notValide que l’attribut de l’objet ne vérifie pas la condition spécifiée.[not(Couleur=‘Vert’)]
trueValide que la valeur boolean de l’attribut est égale à vrai.//Jardin.Legume[Hivernal=true()]
falseValide que la valeur boolean de l’attribut est égale à faux.//Jardin.Legume[Hivernal=false()]
lengthVérifie la condition sur la longueur d’un attribut de type chaîne de caractères.//Jardin.Legume[length(Couleur)>4]

(1) Ceci est utilisé lorsqu’on souhaite réaliser des comparaisons entre l’état actuel d’un objet en cours de manipulation avec son correspondant stocké dans la base de données. Un autre cas serait lorsque nous souhaitons directement retourner une instanciation spécifique d’un objet issue d’un héritage (les objets ayant le même identifiant sous Mendix).

Fonctions d’agrégation :

ÉlémentDescriptionExemple
avgRetourne la somme d’une colonne, c’est-à-dire porte sur un attribut unique de type numérique.avg(//Jardin.Legume[Hivernal=true()]/PrixUnitaire)
countDénombre, sous la forme d’un entier,  la présence d’enregistrements répondant aux critères de sélection.count(/Jardin.Legume[Hivernal=true()])
maxRetourne la valeur maximale existante pour un attribut particulier.max(//Jardin.Legume[Hivernal=true()]/PrixUnitaire)
minRetourne la valeur minimale existante pour un attribut particuliermin(//Jardin.Legume[Hivernal=true()]/PrixUnitaire)
sumRetourne la somme de l’attribut sélectionnésum(//Jardin.Legume[Hivernal=true()]/PrixUnitaire)

N’hésitez pas à me faire part de vos commentaires ou suggestions. Et maintenant, c’est à vous de jouer !


Ce billet fait partie d’une série d’articles en provenance du 🇨🇦 sur les bonnes pratiques Mendix et dédié uniquement aux francophones 😜.
Nous mettrons à jour la liste des billets de la série au fur et à mesure de leur publication.


Pas encore de commentaire

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.