SharePoint 2007 : Linq et SharePoint (Comme vous faire aimer le CAML)

Dernièrement j'ai eu l'occasion de voir une video très sympa parlant d'un projet CodePlex que j'avais un peu oublié : LinqToSharePoint. Pour ceux qui ne connaitrait pas, cet outil, crée par Bart De Smet (Ancien MVP C#, Employé Microsoft depuis peu), permet de convertir des requêtes Linq en CAML.

Alors cela fait maintenant près de 8 mois que Thomas vous a parlé de ce projet plein de promesses, il est temps de voir ce qui a été fait :

Histoire de tester, on va exporter des données de la base NorthWind dans SharePoint pour obtenir ces trois listes : Products, Categories et Suppliers

export_base_northwind

L'idée étant qu'un produit est lié à un supplier et à une catégorie. Celà se traduit par une clef étrangère dans la base et par un champ de type "lookup" dans SharePoint.

Une fois l'export terminé et LinqToSharePoint installé, vous avez la possibilité de rajouter ce nouvel élément à votre projet : Linq to SharePoint file

linqtosharepoint_item

Ce fichier va vous permettre de configurer votre accès aux données grâce à un assistant.

linqtosharepoint_wizard

Vous l'aurez compris cet accès se fait par WebServices. Il vous suffit maintenant de spécifier les champs qui vous intéressent et vous pouvez vous mettre à coder !

 linqtosharepoint_wizard_bis

Prenons un exemple pratique : Supposons que je souhaite récupérer tout les produits dont l'Unit Price est supérieur à 100, en triant le tout par ordre Décroissant. Pour au final, afficher le nom du produit et son prix.

En CAML, ca donnerait ceci (grosso modo, pas de gestion d'erreurs, etc.):

System.Text.StringBuilder oSbQuery = new System.Text.StringBuilder();

 

oSbQuery.Append("        <Where>");

oSbQuery.Append("              <Gt>");

oSbQuery.Append("                   <Value Type=\"Currency\">100</Value>");

oSbQuery.Append("                   <FieldRef Name=\"UnitPrice\" />");

oSbQuery.Append("              </Gt>");

oSbQuery.Append("        </Where>");

oSbQuery.Append("        <OrderBy>");

oSbQuery.Append("              <FieldRef Name=\"UnitPrice\" Ascending=\"FALSE\" />");

oSbQuery.Append("        </OrderBy>");

 

string sResultQuery = oSbQuery.ToString();

 

System.Text.StringBuilder oSbViewFields = new System.Text.StringBuilder();

 

oSbViewFields.Append("        <FieldRef Name=\"Title\" />");

oSbViewFields.Append("        <FieldRef Name=\"UnitPrice\" />");

 

string sResultViewFields = oSbViewFields.ToString();

 

SPQuery query = new SPQuery();

query.ViewFields = sResultViewFields;

query.Query = sResultQuery;

 

using (SPWeb web = new SPSite("http://localhost/NorthWind").OpenWeb())

{

    SPList products = web.Lists["Products"];

    SPListItemCollection items = products.GetItems(query);

 

    foreach (SPListItem item in items)

    {

        Console.WriteLine(String.Format("{0} supplied by {1}", item["ProductName"], item["UnitPrice"]));

    }

}

en Linq, ça devient :

var ctx = new SPNorthWindSharePointDataContext();

var req_Products_1 = from p in ctx.Products

                           where p.UnitPrice > 100

                           orderby p.UnitPrice descending

                           select new { Product = p.ProductName, p.UnitPrice };

 

foreach (var p in req_Products_1)

{

      Console.WriteLine(String.Format("{0} costs {1}", p.Product, p.UnitPrice));

}

Desuite, ca fait une sacrée différence :). Et l'affichage des informations est bien plus simple à faire vu q'on travaille directement avec des objets typés.

Dans le même style, voilà une autre requête, où l'on souhaite récupérer tout les produits dont le ProductName commence par C. On remarquera qu'en CAML, il faudra passer le nom interne de la colonne ProductName (à savoir, Title), LinqToSharePoint fait la traduction tout seul.

<Query>

  <Where>

    <BeginsWith>

      <FieldRef Name="Title" />

      <Value Type="Text">C</Value>

    </BeginsWith>

  </Where>

  <OrderBy>

    <FieldRef Name="Title" Ascending="FALSE" />

  </OrderBy>

</Query>

<ViewFields>

  <FieldRef Name="Title" />

  <FieldRef Name="UnitPrice" />

</ViewFields>

var req_Products_2 = from p in ctx.Products

                     where p.ProductName.StartsWith("C")

                     orderby p.ProductName descending

                     select new { Product = p.ProductName, p.UnitPrice };

Bon c'est sympa, mais soyons honnête, il existe beaucoup d'outils pour simplifier la génération de CAML, qu'est que celui ci apporte en plus ?

Encore une fois, voyons par la pratique : Je souhaite récupérer tout les produits dont la catégorie est Beverages et dont le ProductName commence par C.

En CAML, si on veut faire cela proprement, cela nécessitera deux requêtes.

<Query>

  <Where>

    <Eq>

      <Value Type="Text">Beverages</Value>

      <FieldRef Name="Title" />

    </Eq>

  </Where>

</Query>

<ViewFields>

  <FieldRef Name="ID" />

</ViewFields>

et

<Query>

  <Where>

    <And>

      <BeginsWith>

        <FieldRef Name="Title" />

        <Value Type="Text">C</Value>

      </BeginsWith>

      <Eq>

        <Value Type="Lookup">1</Value>

        <FieldRef Name="Category" LookupId="TRUE" />

      </Eq>

    </And>

  </Where>

  <OrderBy>

    <FieldRef Name="Title" Ascending="FALSE" />

  </OrderBy>

</Query>

<ViewFields>

  <FieldRef Name="Title" />

  <FieldRef Name="UnitPrice" />

</ViewFields>

Ca commence à devenir un peu long à écrire tout cet XML, juste pour une reqête relativement simple non ? Heureusement avec LinqToSharePoint ca donne ça :

var req_Products_3 = from p in ctx.Products

                     where p.ProductName.StartsWith("C")

                     && p.Category.CategoryName == "Beverages"

                     orderby p.ProductName descending

                     select new { Product = p.ProductName, p.UnitPrice };

Desuite, on voit la différence ! En interne, la requête est exactement celle que vous avez vu plus haut. LinqToSharePoint s'occupe seul de faire le lien. Notamment par l'utilisation de l'élément Patch dans la requête ci-dessous.

linqtosharepoint_query_visualizer

Bon attention, vous ne pouvez pas non plus faire n'importe quoi avec. Par exemple, l'utilisation de la méthode EndsWith n'est pas supporté. Cette restriction étant du au CAML en lui même.

linqtosharepoint_notimplemented linqtosharepoint_notimplemented_bis    

Bien sur il est possible de contourner cette limitation en encastrant la requête ci-dessous dans une autre requête Linq classique mais par contre ca complexifie l'ensemble.

Et pour finir, sur cette petite démonstration de ce version ALPHA, voilà la requête qui m'a fait installer ce tool sur ma VPC.

Comment faire pour afficher le nom de tout les produits commençant par C, dont la catégorie est Beverages et en plus d'afficher le CompanyName du Supplier ? Evidemment en CAML, ca va commencer à faire une sacré tranche d'XML (dont je vous épargne le Copier/Coller) mais en Linq, cela reste relativement simple (on a même plusieurs façons de pouvoir écrire la requête).

var req_Products_4 = from p in ctx.Products

                    where p.Category.CategoryName == "Beverages"

                    && p.ProductName.StartsWith("C")

                    orderby p.ProductName descending

                    select p;

 

foreach (var p in req_Products_4)

{

    Console.WriteLine(String.Format("{0} supplied by {1}", p.ProductName, p.Supplier.CompanyName));

}

 

var req_Products_5 = from p in ctx.Products

                    where p.Category.CategoryName == "Beverages"

                    && p.ProductName.StartsWith("C")

                    orderby p.ProductName descending

                    select new { Product = p.ProductName, p.Supplier.CompanyName };

 

foreach (var p in req_Products_5)

{

    Console.WriteLine(String.Format("{0} supplied by {1}", p.Product, p.CompanyName));

}

Bien entendu, l'ensemble de ces requêtes sont optimizées afin d'éviter les appels intempestifs à SharePoint. Par exemple, Si vous avez 4 produits appartenant à la même catégorie, il n'effectuera la requête pour obtenir l'ID de la catégorie qu'une seule fois

Par contre, il n'est pas possible de mettre à jour les données via LinqToSharePoint mais apparement, ca ne saurait tardé.

LinqToSharePoint fonctionne aussi bien avec du C# 3.0 que du VB 9.0 et il est possible de se connecter aussi bien par WebServices que par le Modèle Objet.

<Philippe/>

Publié lundi 5 novembre 2007 07:00 par phil
Classé sous , , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

# re: SharePoint 2007 : Linq et SharePoint (Comme vous faire aimer le CAML) @ lundi 5 novembre 2007 11:15

Hummmmmmmmmmmmmm ca donne envie !!!

Je m'etais fait la meme remarque sur ma VPC a mon premier test

Mais bon, j'aime bien le CAML moi :)

Depuis le temps, ca m'est devenue un second langage ...

L'abus de Site definition je pense :D

Vivement une version MS supportée en tout cas ...

Bart a rejoins MS mais pas la division MOSS donc croisons les doigts

themit

About phil

Leader Technique SharePoint chez Winwise, je m'occupe de missions d'expertise autour de WSS 3.0 et MOSS 2007, tout en me tenant au courant de ce qui se fait autour de Asp.net, Silverlight et des technologies Web by Microsoft en général.

Les 10 derniers blogs postés

- WPF : la gestion des dates (Label, TextBlock) par Pierrick's Blog le il y a 2 heures et 15 minutes

- [ASP.NET] - ASP.NET Generated Image par Aurelien's Blog - When ClientSide meets .Net le il y a 3 heures et 43 minutes

- Utiliser le SDK Open XML pour manipuler vos documents Office Open XML par Julien Chable le il y a 4 heures et 56 minutes

- [Silverlight] - Créer un contrôle réutilisable et des propriétés personnalisées. par Danuz le il y a 8 heures et 14 minutes

- Photosynth : Composez et partagez vos scènes ! par Blog technique de Nicolas Boonaert le il y a 8 heures et 17 minutes

- Comment d&#233;bugger un programme de g&#233;n&#233;ration de code utilis&#233; dans VS ? par Matthieu MEZIL le il y a 8 heures et 18 minutes

- Avoir une propriété sur l'object context qui renvoit les sous-entités par Matthieu MEZIL le il y a 8 heures et 30 minutes

- Sortie du SDK 1.1 de Visual Studio 2008 par Michel Perfetti [Miiitch] le il y a 11 heures et 4 minutes

- Skyfire, Silverlight sur votre mobile ! par alex# le il y a 11 heures et 21 minutes

- VSTSDB 2008 GDR CTP16 est arrivé par Noham Choulant le il y a 12 heures et 25 minutes