Accueil

Linq to SharePoint 2013

by Jean-Camille Mercier 10. juin 2015 16:10

La lecture des listes SharePoint est un peu fastidieuse à cause de l'écriture des requêtes CAML et du transfert entres les colonnes des ContentTypes et de nos objets métiers. Le top serait donc qu'on puisse requêter directement en objet et que le mapping soit automatique ! C'est donc ce que propose LINQ to SharePoint dans l'assembly "Microsoft.SharePoint.Linq.dll" que vous trouverez dans le dossier BIN de la ferme.

Le "start guide" de MS est plutôt bien fait et apporte la méthodologie : 

  1. Instancier un DataContext sur le site SharePoint
  2. Créer les objets POCO en les tagant avec les attributs [ContentType] et [Column]
  3. Requêter les liste avec la méthode "GetList()"

Dans les faits, ce n'est pas si simple et il est indispensable d'utiliser SPMetal pour obtenir des classes correctes. Cet outil va introspecter le site SharePoint et va générer toutes les classes pour chaque ContentType trouvé !

  • Génération des POCO

Il suffit de le lancer en ligne de commande avec les arguments "/web" et "/code" :

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\BIN>
spmetal /web:http://dsi-sp-dev-4:50011/PWA /code:F:\Sources\gen.cs

Il va créer un nouveau DataContext pour donner accès à toutes les listes trouvées :

public partial class FlashDataContext : DataContext
{
    public FlashDataContext(string requestUrl) : base(requestUrl) { }

    [ListAttribute(Name = "FlashReportLines")]
    public EntityList<FlashIndicateur> FlashReportLines
    {
        get { return this.GetList<FlashIndicateur>("FlashReportLines"); }
    }

    ...
}

 Une classe de base pour nos POCO qui implémente les interfaces de tracking et de changement 

[ContentTypeAttribute(Name = "Element", Id = "0x01")]
[DerivedEntityClassAttribute(Type = typeof(FlashIndicateur))]
[DerivedEntityClassAttribute(Type = typeof(FlashReportData))]
public partial class ElementBase : ITrackEntityState, ITrackOriginalValues, 
                                   INotifyPropertyChanged, INotifyPropertyChanging
{
    EntityState ITrackEntityState.EntityState
    {
        get { return this._entityState; }
        set
        {
            if ((value != this._entityState))
                this._entityState = value;
        }
    }

    IDictionary ITrackOriginalValues.OriginalValues
    {
        get
        {
            if ((null == this._originalValues))
                this._originalValues = new Dictionary();

            return this._originalValues;
        }
    }

    ...
}

Et ensuite les classes mappées sur les colonnes des types de contenu :

[ContentType(Name = "Ugap_FlashIndicateur", Id = "0x01000666D390130D234091AC3CD209144CB5")]
public partial class FlashIndicateur : ElementBase
{
    [ColumnAttribute(Name = "Commentaire", Storage = "_commentaire", FieldType = "Note")]
    public string Commentaire
    {
        get { return this._commentaire; }
        set
        {
            if ((value != this._commentaire))
            {
                this.OnPropertyChanging("Commentaire", this._commentaire);
                this._commentaire = value;
                this.OnPropertyChanged("Commentaire");
            }
        }
    }

    ...
}

Ce qui est pratique c'est que si vous avez une colonne de choix sur liste, SPMetal va automatiquement créer l'énumération correspondante !

public enum Evaluation : int
{
    None = 0,
    Invalid = 1,

    [ChoiceAttribute(Value = "Bien")]
    Bien = 2,
    [ChoiceAttribute(Value = "Normal")]
    Normal = 4,
    [ChoiceAttribute(Value = "Mauvais")]
    Mauvais = 8,
}

[Column(Name = "Evaluation", Storage = "_evaluation", Required = true, FieldType = "Choice")]
public Evaluation Evaluation
{
    get { return this._evaluation; }
    set
    {
        if ((value != this._evaluation))
        {
            this.OnPropertyChanging("Evaluation", this._evaluation);
            this._evaluation = value;
            this.OnPropertyChanged("Evaluation");
        }
    }
}

Vous pouvez consulter le mapping des types Sharepoint / dotNet sur cette page

Il est aussi intéressant de pouvoir attaquer les colonnes de base comme l'auteur ou la date de modification, vous trouverez la liste des noms internes ici.

  • ForeignKey

Par contre SPMetal a du mal générer les relations 0..n entre 2 listes SharePoint. Pour rappel, dans la liste "enfant" il faut créer une colonne de Lookup qui va aller chercher dans la table "parente". Du côté objet, le parent aurant une List<enfant> et l'enfant aura un getter vers le parent. C'est l'attribut [Associtation] et sa propriété "MultivalueType" qui va ensuite rassembler le tout.

Parent.List<enfants> :
private EntitySet<FlashIndicateur> _lines;

[AssociationAttribute(Name = "FlashReportId", Storage = "_lines", ReadOnly = true,
                      MultivalueType = AssociationType.Backward, List = "FlashReportLines")]
public EntitySet<FlashIndicateur> Indicateurs
{
    get { return this._lines; }
    set { this._lines.Assign(value); }
}
Enfant.Parent :
private EntityRef<ElementBase> _flashReportId;

[AssociationAttribute(Name = "FlashReportId", Storage = "_flashReportId", 
                      MultivalueType = AssociationType.Single, List = "FlashReports")]
public ElementBase FlashReportId
{
    get { return this._flashReportId.GetEntity(); }
    set { this._flashReportId.SetEntity(value); }
}
  •  Utilisation

 Maintenant vous pouvez attaquer les listes SharePoint exactement comme un BDD SQL avec Entity Framework !!

tring idProject = "b2089517-e1e4-e411-9414-005056893e56";
// Trouver le FlashReport pour ce projet
using (var pwaSite = new FlashDataContext(SPContext.Current.Web.Url))
{
    FlashReportData flash = pwaSite.FlashReports.FirstOrDefault(f => f.IdProjet == idProject);
    // Combien a-t-il d'indicateurs ?
    int nbIndic = flash.Indicateurs.Count();
}

 

Sources :
https://msdn.microsoft.com/en-us/library/ff798478.aspx
http://blogs.msdn.com/b/ocarpen/archive/2010/12/23/good-practices-with-linq-to-sharepoint.aspx