Join et ProjectedFields

Réalisation de jointures en CAML


Une évolution majeure de SharePoint 2010 par rapport à son prédécesseur est l'introduction des jointures dans les requêtes CAML. Effectivement, si vous avez déjà utilisé le CAML sous SharePoint 2007, vous vous serez certainement rendu compte qu'il était impossible d'exécuter des jointures grâce aux requêtes CAML. Cela limitait considérablement l'utilisation des requêtes faisant intervenir plusieurs tables.

Avec SharePoint 2010, il est maintenant possible d'utiliser des join et des projected fields pour réaliser des jointures entre tables. Nous allons développer une Visual Web Part qui se chargera d'exécuter différentes requêtes CAML et d'afficher le résultat pour voir toute la puissance des jointures (pour ceux qui ne connaissent pas ce concept en SQL).

Commencez par créer un nouveau projet de type Visual Web Part que vous nommerez AreaProg.VisualWebParts.JoinProjectedFields :



Étant donné que cette solution déploiera des fichiers dans un des répertoires "systèmes" de SharePoint, laissez "Deploy as farm solution" coché. Dans l'arborescence de votre projet, repérez VisualWebPart1 et supprimez la. Cliquez ensuite avec le bouton droit sur votre projet et choisissez Add > New Item et choisissez Visual Web Part. Nommez la "JoinProjectedFields" :



Double-cliquez maintenant sur le fichier Elements.xml pour modifier le nom du groupe dans lequel la WebPart ira s'insérer. Remplacez donc Custom par AreaProg :



Ouvrez maintenant le fichier JoinProjectedFields.webpart et modifiez la propriété Title sur "Join et Projected Fields" et la propriété description sur "Utilisation des jointures en CAML" :



Avant d'attaquer le code du projet, nous allons décrire l'architecture du site que nous allons utiliser pour démontrer l'utilisation des jointures. Nous allons disposer de 3 listes.

La première se nommera Pays et contiendra les valeurs suivantes :



Nous avons ensuite la liste Villes :



Le champ Pays est de type Lookup et pointe sur la liste des pays vue précédemment. Ceci est une condition pour effectuer des jointures entre deux listes. Effectivement, le champ servant à effectuer la jointure devra absolument être de type Lookup, vous verrez ensuite pourquoi. Enfin, nous avons la liste Monuments :



Le champ Ville de cette liste est également un champ de type Lookup pointant vers la liste des villes.

Le but de cette architecture est assez simple. Nous avons la liste Monuments qui va nous permettre de stocker des monuments célèbres tels que le Colisée, la tour Eiffel,... Chacun de ces monuments se trouve dans une ville qui sera contenue dans la liste Villes. Et chacune de ces villes se trouvera dans un pays contenu dans la liste Pays. Nous n'exigeons pas que les Lookup contiennent des valeurs pour démontrer la différence entre les jointures de type INNER et LEFT. Vous avez certainement remarqué que le monument Projet n'a pas de ville. Effectivement, ce monument est un projet et la ville de ce monument n'a pas encore été choisie, nous n'avons donc sélectionné aucune ville.

Pour ceux qui ne savent pas ce que sont les jointures, nous allons expliquer cela tout de suite. Les personnes savant à quoi sert INNER JOIN, LEFT JOIN,... en SQL peuvent passer ce passage car il ne leur apprendra rien.

Le problème induit par cette architecture est assez simple. Imaginez que vous vouliez récupérer tous les monuments contenus dans la liste mais également les informations sur la ville et le pays de ce monument. La technique en SharePoint 2007 serait de faire une première requête récupérant tous les monuments, une deuxième requête récupérant les villes et une troisième requête récupérant les pays. Ensuite, selon un système de stockage en mémoire à base de classes, vous vous seriez arrangé pour que les monuments soient liés aux villes et les villes aux pays.

Cela aurait bien fonctionné mais n'aurait pas été très performant. Dans SharePoint 2010, la technique utilisée sera les jointures. Effectivement, il va être possible de réaliser les 3 requêtes en une seule. Pour cela, la requête devra dire :

Récupérer tous les monuments en liant les villes de ces monuments et les pays de ces villes

La requête renverrait donc toutes les informations d'un coup. Bien entendu, la jointure devra être effectuée sur une certaine valeur. En français cela donnerait :

Récupérer le monument X ainsi que la ville du monument X

Donc si nous nous basons sur notre architecture actuelle et que nous voulions récupérer la Tour Eiffel, nous dirions :

Récupérer le monument pour lequel le titre vaut Tour Eiffel et récupérer la ville dont le titre vaut la valeur du champ Ville du monument (donc Paris).

Sous SharePoint 2007, il aurait été impossible de traduire cette requête en CAML. C'est maintenant faisable en SharePoint 2010.

Avant de passer à l'exemple, il faut savoir une dernière chose. En CAML, il est possible d'effectuer des jointure de type INNER ou de type LEFT. La différence entre ces deux types de jointures est très importante. Nous verrons cela lors de l'explication de notre exemple. Revenez donc dans votre classe et ajoutez la directive suivante :



Déclarez ensuite la fonction suivante :



Le but de cette fonction sera d'afficher tous les monuments ainsi que les informations sur leur ville et leur pays. Le paramètre type va permettre de définir si la jointure sera de type LEFT ou INNER et nous permettra de comprendre la différence. La première chose à faire dans cette fonction est de récupérer une référence à la liste et de créer un objet SPQuery pour réaliser la requête :



Nous allons nous baser sur la liste Monuments car ce sont les informations des monuments et les informations liées à ce monument que nous voulons récupérer. Nous allons maintenant définir la propriété Joins de la requête. C'est dans cette propriété que nous allons définir le format de la jointure. Nous allons directement afficher le code du Joins et nous l'expliquerons après :



Comme nous venons de le dire, la propriété Joins va permettre de contenir du code CAML indiquant comment la ou les jointures seront effectuées. Ici, pour commencer, nous n'allons effectuer qu'une seule jointure. En gros, nous allons joindre la table Monuments à la table Villes grâce au champ Ville de la table monument. Ainsi, nous récupérerons les informations de chaque monument ainsi que les informations sur la ville dans laquelle se trouvent ces monuments.

Une jointure est donc introduite grâce à l'élément Join. Cet élément attend deux paramètres. Le premier se nomme Type et permet de définir le type de la jointure. La valeur de cet attribut peut soit être LEFT soit INNER. Nous verrons plus tard ce que cela représente. L'attribut ListAlias quant à lui va permettre de donner une "autre nom" à la table que nous joignons. Ici, la première table est bien entendu la table sur laquelle la requête sera exécutée, à savoir Monuments. Nous allons ensuite joindre la table Villes et nous lui donneront comme alias VillesList.

Nous devons ensuite tester l'égalité. Pour cela, nous utiliserons donc l'élément Eq pour introduire la comparaison. Cet élément doit alors contenir deux éléments FieldRef qui serviront chacun à quelque chose de bien précis. Le premier FieldRef va permettre d'indiquer quel champ dans la première liste (Monuments) contiendra la référence à la deuxième liste (Villes). Ce champ doit obligatoirement être un Lookup. Ici, nous indiquons donc que le nom du champ est Ville (étant le nom du Lookup de la liste Monuments). Nous ajoutons ensuite RefType='Id' pour indiquer que la comparaison sera faite sur l'id que ce champ contient. Effectivement, un champ de type Lookup contient une référence vers l'élément cible sous forme de "ID;#Valeur". RefType='Id' permet donc de faire la comparaison avec l'ID.

Le deuxième élément FieldRef va nous permettre d'indiquer le champ dans la deuxième liste auquel sera comparé la valeur du Lookup. Étant donné que nous extrayons l'ID du champ Lookup, nous ferons bien sur référence au champ ID de la liste Villes. Nous faisons donc passer l'alias de la liste Villes comme valeur de l'attribut List qui permet de spécifier à quelle liste appartient le champ auquel on fait référence. Nous indiquons enfin que nous récupérons le champ ID de cette liste pour effectuer la comparaison.

Et c'est tout, la jointure est effectuée. Mais il reste une question à se poser... A aucun moment nous avons définit que c'est avec la liste nommées Villes que nous ferons la jointure. Nous avons bien donné un alias à la deuxième liste (à savoir VillesList), mais à aucun moment nous n'avons indiquer clairement que la jointure sera effectuée avec la liste Villes. En fait, cela est dit implicitement. Effectivement, quand nous spécifions que la comparaison sera effectuée grâce au Lookup field Ville, la requête va directement "regarder" vers quelle liste ce champ pointe. Il va "voir" que c'est vers la liste Villes et c'est donc avec cette liste que la jointure sera effectuée.

Nous allons maintenant devoir utiliser des projected fields. En fait, quand vous allez effectuer la jointure, tous les champs des 2 tables seront présents, mais il faudra bien trouver une manière de savoir les afficher. Par exemple, actuellement, notre jointure lie la table Monuments à la table Villes, mais comment faire pour récupérer le champ Title d'une de ces deux tables. Effectivement, ce champ existant dans les deux tables, il pourrait y avoir un risque de confusion. C'est donc là qu'interviennent les projected fields. Par défaut, seuls les champs de la première liste seront récupérés, il nous faudra ensuite "projeter" les champs de la deuxième liste pour pouvoir les récupérer. Tapez donc ce code à la suite du précédent :



Nous allons ici définir quel champ dans la liste liée nous allons récupérer. La syntaxe est très simple. Chaque champ projeté sera identifié grâce à un élément Field. L'attribut Name permet de définir le nom qu'il aura au final dans l'objet model. Ici, nous ne pouvons pas mettre Title étant donné que ce nom de champ est déjà utilisé pour le Title du monument. Nous devons ensuite définir que le type de ce champ est un Lookup. Ensuite, nous avons l'attribut List qui permet de définir de quelle liste nous récupérons le champ. Il faut ici faire passer l'alias de cette liste, nous faisons donc passer celui de la liste Villes. Ensuite, l'attribut ShowField permet de faire passer le nom du champ à récupérer dans la liste identifiée par l'attribut List. Le premier élément va donc récupérer le champ Title de la liste Villes et le placer dans un champ nommé VilleTitle. Le deuxième élément va récupérer le champ Superficie de la liste Villes et le placer dans le champ nommé VilleSuperficie.

Nous définissions ensuite l'élément ViewFields qui va permettre de définir quels champs seront renvoyés par la requête CAML :



Nous récupérons donc ici tout simplement le champ nommé Title, le champ Entrepreneur, le champ VilleTitle et le champ VilleSuperficie. Vient ensuite le code permettant d'afficher tout cela dans la WebPart :



Ce code n'est pas très compliqué. Nous utilisons l'objet query avec la méthode GetItems de la liste pour récupérer les éléments correspondant à la requête définie. Nous devons ensuite vérifier si le champ VilleSuperficie existe car cela n'est pas certain quand nous faisons une requête du type LEFT. Nous allons voir ça très prochainement. Nous récupérons ensuite le champ en faisant des opérations dessus car étant un champ du type Lookup, il renvoi ID;#Valeur, nous devons donc uniquement récupérer la partie Valeur, nous faisons donc un Substring depuis la position de ;# + 2.

Placez vous maintenant dans la fonction Page_Load et appelez cette fonction de cette manière :



Nous appelons donc la même fonction mais le premier appel réalisera une requête de type LEFT alors que le deuxième appel réalisera une requête de type INNER. Voici le résultat renvoyé par ces deux requêtes :



La différence entre les deux requêtes est que celle de type LEFT va renvoyer un élément de plus que celle du type INNER. En fait, la raison est assez simple et va concerner l'élément Projet. Effectivement, celui-ci existe bien dans la liste Monuments mais son champ Ville n'a pas été défini. Il n'y a donc aucune concordance entre la valeur de ce champ et un élément dans la liste des villes. Dans ce cas, si aucune correspondance n'est trouvée entre les listes, une jointure de type INNER ne renverra carrément pas l'élément. Par contre, une jointure du type LEFT renverra quand même l'élément avec les propriétés définies dans la liste Monuments, mais étant donné qu'aucune correspondance ne sera trouvée dans la liste Villes, elle ne renverra pas les champs de cette liste. C'est en fait pour cela que nous avons testé si le champ VilleSuperficie existe bien dans le résultat avant d'effectuer des opérations dessus.

Passons maintenant à la deuxième requête. Celle-ci va effectuer une requête relativement similaire sauf qu'elle joindra également la liste Pays à la requête pour récupérer les informations du pays dans lequel se trouve la ville du monument. Tapez donc le squelette de la fonction comme ceci :



Nous ferons encore passer le type de la jointure en paramètre pour bien afficher la différence de type. Comme précédemment, nous commençons le code de la fonction par la récupération de la liste et la création d'un nouvel objet SPQuery :



Tapez donc ensuite le code suivant pour effectuer la jointure des 3 tables :



Nous effectuons ici deux jointures. La première de ces deux jointures est la même que celle que nous avons réalisée dans la fonction précédente (relisez donc l'explication de celle-ci si vous ne comprenez pas). Une fois la liste Villes jointes à la liste Monuments, nous allons devoir joindre la liste Pays à la liste Villes sur base du champ Pays de la liste Villes. Nous effectuons donc une jointure du type passé en paramètre. Nous appelons ensuite la liste Pays (déclarée implicitement par le Lookup) PaysList. Le premier FieldRef permet donc de définir une référence vers le Lookup permettant de faire la jointure. Ici, il s'agit du champ Pays dans la liste Villes. Etant donné que cette liste n'est pas celle sur laquelle la requête sera exécutée, nous devons utiliser l'attribut List de FieldRef pour indiquer l'alias de la liste contenant le Lookup, donc VillesList. Le deuxième FieldRef indique simplement que la comparaison se fera avec le champ ID de la liste des pays.

Passons maintenant au projection des champs :



Nous projetons donc simplement les champs de la liste Villes et de la liste Pays en leur donnant des noms explicites. Nous définissons ensuite le ViewFields de la requête pour définir quel champ la liste devra retourner :



Enfin, comme dans la fonction précédente, nous bouclons sur les éléments renvoyés par la requête de nous en affichons le contenu :



Dans votre fonction Page_Load, vous appelerez cette méthode de cette manière :



Ce qui aura pour effet d'afficher :



Toutes les informations ont bien été récupérées et affichées. Et comme toute à l'heure, vous pouvez remarquer que la requête de type LEFT renvoie un élément en plus mais que la valeur de ces champs n'a pas été initialisée. Du moins pour les champs contenus dans les tables n'ayant pas pu être jointes.

Passons maintenant à un autre requête qui va permettre d'afficher toutes les informations du monument nommé Tour Eiffel. Tapez d'abord le squelette de la fonction :



Pour ce qui est de la jointure, des champs projetés et des champs récupérés, nous utiliserons les même définition que pour la fonction précédente :



Si vous ne comprenez pas ces lignes de code, nous vous conseillons de relire les paragraphes précédents. La nouveauté se situe dans la suite du code :



Ici, nous définissons le coeur de la requête qui va permettre de poser des conditions à la récupération des éléments. Nous introduisons donc la condition avec l'élément Where. Le but de cette requête est de récupérer les éléments pour lesquels le champ Title (de la liste Monument) vaudra Tour Eiffel. Nous utilisons donc très simplement les éléments Eq, FieldRef et Value pour effectuer la condition. Pour ce qui est du nom du champ, nous passons bêtement Title pour indiquer qu'il s'agit du titre de l'élément de la liste de base (Monuments). La suite du code est la suivante :



Nous trichons un peu dans ce code. Effectivement, nous savons que l'élément existe, nous récupérons donc directement l'élément à l'index 0. Nous savons également que les champs Ville et Pays sont initialisés, nous ne testons donc pas leur existence. Appelez maintenant cette fonction depuis Page_Load de cette manière :



L'exécution de cette requête produira le résultat suivant :



L'élément Tour Eiffel ainsi que les valeurs de tous ces champs ont bien été récupérés. Passons maintenant à la dernière fonction qui va nous permettre d'effectuer des conditions sur les champs des listes jointes. Voici donc le code entier de cette fonction :



Nous voulons récupérer les éléments dont la superficie du pays excède la valeur passée comme premier paramètre de la fonction et dont la superficie de la ville excède la valeur passée en deuxième paramètre. Pour ce qui est des jointures, des champs projetés et des champs récupérés, rien ne change. La différence se trouve dans la requête Where permettant de récupérer les éléments :



Comme vous le voyez, cette requête est très basique et extrêmement simple. Elle effectue un And pour les conditions. La seule notion à tenir à l'oeil est qu'ici, nous pouvons utiliser le nom des champs projetés pour effectuer les comparaisons. Cela ne crée donc aucun problème d'utiliser PaysSuperficie et VilleSuperficie pour effectuer les comparaisons. Appelez cette fonction de cette manière dans la fonction Page_Load :



Ce qui aura pour effet d'afficher cela :



Les éléments sont donc bien récupérés en fonction des valeurs des superficies des villes et des pays. La première fonction doit récupérer les éléments dont le pays a une superficie supérieure à 50000 km² et dont la ville a une superficie supérieure à 5000 km². Il n'y a donc que la Tour Eiffel qui apparait. L'autre requête, elle, récupère tous les monuments dont le pays a une superficie supérieure à 0 km² (donc tous) mais il faut également que la superficie de la ville soit supérieure à 200 km², c'est à dire tous les éléments également.

Comme vous avez pu le constater, le méchanisme de jointure de tables est très simple. Si vous n'arrivez pas à faire fonctionner l'exemple ci dessus, nous vous invitons à le télécharger en cliquant sur le lien... ci dessous.

Télécharger