Kolumne: SharePoint ganz praktisch

LINQ to SharePoint und SPMetal
Kommentare

Eigene SharePoint-Anwendungen verwenden in vielen Fällen benutzerdefinierte SharePoint-Listen für die Datenablage. Der programmgesteuerte Zugriff auf die Listendaten kann auf unterschiedlichen Wegen erfolgen. Einen sehr einfachen Weg bietet der Zugriff über LINQ to SharePoint.

Geschäftliche Anwendungen, auch als „Line of Business“ Applications (kurz: LOB) bezeichnet, benötigen in der Regel einen Datenspeicher. Setzt man integrierte SharePoint-Anwendungen um, können als Hintergrundspeicher benutzerdefinierte Listen verwendet werden. Diese Listen sind dann aus dem Code heraus zu verwalten. Dazu müssen typischerweise alle CRUD-(Create-, Read-, Update- und Delete-)Operationen unterstützt werden. Um die notwendigen Listenoperationen anbieten zu können, existieren im Grunde die folgenden vier Möglichkeiten:

  1. Direkter Zugriff über das Listen-API (SPListItem und SPList)
  2. Erstellung einer eigenen Datenzugriffsebene
  3. REST-basierte Zugriffe
  4. LINQ to SharePoint

Die erste Option greift einfach über das SharePoint-Listen-API auf die zugehörigen Listen zu. Dabei finden diese Zugriffe meist verstreut im Code statt und sind nicht zentralisiert. Dieses Verfahren ist lediglich für (sehr) kleine Anwendungen zu empfehlen. Wenn schon direkt das Listen-API verwendet wird, sollten die Zugriffe in einer Datenzugriffsebene gekapselt werden, wie es die zweite Option vorsieht. Die Datenzugriffsebene ist dabei die einzige Stelle im Programmcode, die direkt auf SharePoint-Listen zugreift. Somit kann zentral darauf reagiert werden, wenn sich die Listenstruktur in der Zukunft ändert, etwa durch Hinzufügen einer neuen Spalte. Eine weitere Interaktionsmöglichkeit besteht seit SharePoint 2010 über REST. Alle Einträge einer Liste können über eine REST-Schnittstelle verwaltet werden. Dieser Weg eignet sich immer dann, wenn kein SharePoint-API direkt verwendet werden kann. Dies ist beispielsweise dann der Fall, wenn von einer Nicht-Microsoft-Plattform auf die Daten zugegriffen werden muss. Um nicht selbst eine Datenzugriffsebene umsetzen zu müssen, kommt nun LINQ to SharePoint ins Spiel. Im Zusammenspiel mit SPMetal, einem Werkzeug für die Generierung von Entitätsklassen, kann sehr schnell die notwendige Datenzugriffsebene realisiert werden.

LINQ to SharePoint

LINQ to SharePoint wurde offiziell mit der SharePoint-Version 2010 eingeführt. Die Syntax entspricht der üblichen LINQ-Syntax anderer Anbieter und erleichtert somit die Verwendung im Vergleich zu CAML-Abfragen. Außerdem ermöglicht LINQ to SharePoint einen typisierten Zugriff auf Listen. Bevor jedoch typisiert auf Listen und deren Elemente zugegriffen werden kann, sind einige Vorbereitungen zu treffen, die im Folgenden beispielhaft beschrieben werden. Die zunächst manuellen Schritte dienen lediglich der Verdeutlichung der Funktionsweise.

Der erste Schritt besteht darin, für die gewünschten Listen so genannte Entitätsklassen zu erstellen, die die Struktur eines Listeneintrags widerspiegeln. Listing 1 zeigt dazu ein einfaches Beispiel: Die Klasse Aufgabe wird dazu verwendet, die Listeneinträge einer Standard-SharePoint-Aufgabenliste (Task List) zu kapseln. Aus Gründen der Übersichtlichkeit werden lediglich die Spalten Id, Titel und Aufgabenstatus (Task Status) übernommen. Selbstverständlich kann über diese Klasse später nur auf die definierten Feldinformationen zugegriffen werden. Werden weitere Daten aus anderen Spalten benötigt, müssen die entsprechenden Felder ebenfalls, wie gezeigt, in der Klasse definiert werden. Wichtig sind die Klassen- und Eigenschaftsattribute ContentType und Column. Mit dem Attribut ContentType wird der zu kapselnde Inhaltstyp festgelegt. In diesem Fall handelt es sich um eine Aufgabenliste, daher wird die Eigenschaft Name auf Task und die Eigenschaft Id auf 0x0108 gesetzt. Die Eigenschaft Id muss auf den entsprechenden SharePoint-Inhaltstyp (Content Type) der Quellliste gesetzt werden. Eine Übersicht über die verfügbaren SharePoint-Inhaltstypen ist hier erhältlich.

[ContentTypeAttribute(Name = "Task", Id = "0x0108")]
public partial class Aufgabe
{
  private string title = string.Empty;
  private Nullable taskStatus;
  private Nullable id;
  [ColumnAttribute(Name = "ID", Storage = "id", 
   ReadOnly = true, FieldType = "Counter")]
  public Nullable Id
  {
    get
    {
      return id;
    }
    set
    {
      id = value;
    }
  }
  [ColumnAttribute(Name = "Title", Storage = "title",  
   Required = true, FieldType = "Text")]
  public String Title
  {
    get
    {
      return title;
    }
    set
    {
      title = value;
    }
  }
  [ColumnAttribute(Name = "TaskStatus", Storage = "taskStatus",  
       FieldType = "Choice")]
  public Nullable TaskStatus
  {
    get
    {
      return taskStatus;
    }
    set
    {
      taskStatus = value;
    }
  }
}

Die Klasse besteht im Wesentlichen aus öffentlichen Eigenschaften. Dabei muss es sich um vollständige Eigenschaften handeln, da über das Attribut Storage die zu verwendende interne Variable angegeben werden muss. Weiterhin müssen der Name der Spalte (Attribut Name) sowie der Feldtyp (Attribut FieldType) spezifiziert werden. Wurde die Klasse wie gezeigt angelegt, stellt diese eine vollständige Entitätsklasse dar und kann innerhalb einer LINQ-to-SharePoint-Abfrage verwendet werden. Das wird im Beispiel aus Listing 2 demonstriert: Die generische Listenklasse EntityList kann mit der zuvor definierten Klasse Aufgabe typisiert werden. Danach kann die angelegte Instanzvariable tasks als Datenquelle für eine LINQ-Abfrage verwendet werden. Diese Vorgehensweise ist vergleichbar mit einer LINQ-to-Objects-Abfrage.

public static void UsingLINQ2SharePoint()
{
  using (DataContext site = new DataContext("URL"))
  {
    EntityList tasks = site.GetList("Tasks");
    var query = from task in tasks select task;
    foreach (Task item in query)
      Console.WriteLine(item.Title);
  }
}

Verwendung von SPMetal

Wie bereits weiter oben angedeutet, kann die Anlage der notwendigen Entitätsklassen automatisiert erfolgen, denn die manuelle Anlage könnte bei größeren Listen mit vielen Eigenschaften sehr mühselig werden. Die gewünschten Klassen können daher auch so generiert werden, dass ihre manuelle Anlage entfällt. Die Generierung erfolgt über das Kommandozeilenprogramm SPMetal, das sich im Ordner %ProgramFiles%Common FilesMicrosoft Sharedweb server extensions14BIN befindet (bei SharePoint 2013 ist der Unterordner 14 durch 15 zu ersetzen). SPMetal generiert für SharePoint-Objekte entsprechende Entitätsklassen und ermöglicht somit einen objektorientierten und typisierten Zugriff auf Inhalte der SharePoint-Inhaltsdatenbank. Selbst wenn das primäre Einsatzgebiet der Klassen im Bereich der LINQ-to-SharePoint-Abfragen liegt, unterstützen die Klassen auch weitere typische Listenoperationen. Die generierten Entitätsklassen ermöglichen die Aktualisierung von Einträgen, das Löschen von bestehenden Einträgen und die Neuanlage von Listeneinträgen. Außerdem werden konkurrierende Zugriffe auf Elemente erkannt und gelöst. Der folgende Code zeigt einen beispielhaften Aufruf des Kommandos:

spmetal.exe /web:http://win-fd3baqooh2l 
/code:c:samplesLinqToSharePoint.cs  /namespace:SPKolumne

Über den Parameter code wird der gewünschte Dateiname für die zu erstellende Codedatei vorgegeben. Als Dateiendung kann entweder .cs oder .vb eingesetzt werden, entsprechend der Dateiendung wird dann eine VB.NET- oder eine C#-Klasse erstellt. Die generierte Datei kann schließlich in ein .NET-Projekt eingebunden und verwendet werden. Damit die generierte Klasse kompiliert werden kann, ist eine zusätzliche SharePoint-Bibliothek notwendig. Daher muss das zugehörige Projekt neben der üblichen Referenz auf die Bibliothek Microsoft.SharePoint auch noch die Bibliothek Microsoft.SharePoint.Linq referenzieren. Diese befindet sich ebenfalls im Standardordner C:Program FilesCommon Filesmicrosoft sharedWeb Server Extensions14ISAPI (für SharePoint 2013 ersetzen Sie 14 durch 15). Wie die generierte Klasse innerhalb des Codes verwendet wird, verdeutlicht der folgende Code. Hier werden alle Seitennamen aus der Seitenbibliothek ausgegeben, deren Namen mit „home“ beginnen.

public static void TestLINQ()
{
  LinqToSharePointDataContext lqCtx = new LinqToSharePointDataContext(...);
  foreach (WikiPage page in lqCtx.SitePages.Where(p=>p.Name.StartsWith("home")))
    Console.WriteLine(page.Name);
}

Weitere Listenoperationen

Neben dem einfachen Abrufen von Listendaten aus einer Liste ermöglicht ein LINQ-to-SharePoint-Datenkontext alle wesentlichen Listenoperationen. Über den Kontext können neue Einträge erstellt und bestehende geändert oder auch gelöscht werden. In Listing 3 sind all diese Vorgänge in den entsprechenden Methoden beispielhaft realisiert. Als Beispielliste wird hier eine Aufgabenliste verwendet. Die erste Methode Add erstellt eine neue Aufgabe und fügt sie über den Aufruf MeineAufgaben.InsertOnSubmit(task) der Liste hinzu. Dieser Aufruf ist wichtig, ohne ihn hat der Datenkontext keine Kenntnis über die neue Aufgabe. Sollen die offenen Änderungen des Datenkontexts übernommen werden, muss jeweils die SubmitChanges-Methode aufgerufen werden. Die beiden anderen Methoden folgen diesem Schema. Die Update-Methode greift auf eine bestehende Aufgabe zu und aktualisiert den Body-Text. Innerhalb der Delete-Methode wird ein bestehender Eintrag über MeineAufgaben.DeleteOnSubmit(task) für die Löschung markiert. Durch den Aufruf von SubmitChanges wird der Eintrag dann endgültig aus der Liste gelöscht. Wie anhand dieser Beispiele deutlich wurde, werden alle Listenoperationen unterstützt. Durch die typisierten Klassen entfällt ebenfalls der ansonsten umständliche Zugriff auf Listendaten über die Spaltennamen.

public static void Add()
{
  LinqToSharePointDataContext lqCtx = new LinqToSharePointDataContext(...);
  MeineAufgabenTask task = new MeineAufgabenTask();
  task.Title = "ShartePoint Kolumne";
  task.AssignedTo = "mzhou";
  task.StartDate = DateTime.Now.AddDays(3);
  task.DueDate = DateTime.Now.AddDays(10);
  lqCtx.MeineAufgaben.InsertOnSubmit(task);
  lqCtx.SubmitChanges();
}
public static void Update()
{
  LinqToSharePointDataContext lqCtx = new LinqToSharePointDataContext(...);
  MeineAufgabenTask task = lqCtx.MeineAufgaben.
      Where(t => t.Title.Equals("ShartePoint Kolumne")).FirstOrDefault();
  task.Body = "Aktualisierung eines Eintrags";
  lqCtx.SubmitChanges();
}
public static void Delete()
{
  LinqToSharePointDataContext lqCtx = new LinqToSharePointDataContext(...);
  MeineAufgabenTask task = lqCtx.MeineAufgaben.
      Where(t => t.Title.Equals("ShartePoint Kolumne")).FirstOrDefault();
  lqCtx.MeineAufgaben.DeleteOnSubmit(task);
  lqCtx.SubmitChanges();
}

Synchronisierung des Entitätsmodells

Da sich typischerweise im Laufe der Entwicklung neue oder geänderte Anforderungen ergeben – selbst wenn zuvor viel Zeit in eine Anforderungs- und Leistungsbeschreibung investiert wurde – ist es sinnvoll, den Erstellungsprozess der Entitätsklassen vor jedem Build-Vorgang automatisiert auszuführen. Das garantiert, dass die SharePoint-Objekte und die zugehörigen Entitätsklassen immer synchron sind. Dazu kann in Visual Studio ein Pre-Build-Skript hinterlegt werden. Diese Skript- bzw. Batchdatei wird jedes Mal ausgeführt, bevor das Projekt neu kompiliert wird. Dabei handelt es sich im Grunde genommen um eine gewöhnliche Batchdatei mit der Endung *.bat. Dem Beispielprojekt in Visual Studio wird daher zunächst eine Textdatei namens LinqToSharePointEntities.bat mit dem Inhalt aus Listing 4 hinzugefügt. Damit die Datei ordnungsgemäß verarbeitet werden kann, muss sie im Unicode-(UTF-8-without signature-)Format gespeichert werden. Das kleine Skript prüft zunächst, ob eventuell eine vorherige Version existiert. Ist dies der Fall, wird die Datei in den Unterordner Backup verschoben. Damit die Dateien im Sicherungsverzeichnis nicht verloren gehen, wird dem Dateinamen ein Zeitstempel hinzugefügt. Steht die vorbereitete Skriptdatei im Visual-Studio-Projekt bereit, muss sie vor jedem Start des Build-Prozesses ausgeführt werden. Um das zu automatisieren, muss in die Projekteigenschaften gewechselt (rechte Maustaste auf das Projekt und dann Properties auswählen) und das Register Build Events geöffnet werden (Abb. 1).

Abb. 1: Automatischer Aufruf von SPMetal

ECHO ON
ECHO SPMetal: Entitaetsklassen werden aktualisiert ...
SET TIMESTAMP=%DATE:.=%-%TIME::=%
SET TIMESTAMP=%TIMESTAMP:,=%
IF NOT EXIST .Backup MkDir .Backup
IF EXIST LinqToSharePoint.cs (
  copy LinqToSharePoint.cs .BackupLinqToSharePoint-%TIMESTAMP%.cs
)
SPMetal /web:http://sps2013-zhou/linqtosps /code:LinqToSharePoint.cs
        /namespace:SPKolumne
ECHO Aktualisierung wurde beendet.

Zusammenfassung

Die heutige Kolumne hat einen weiteren alternativen Zugriffsweg auf Listendaten vorgestellt. Der Einsatz von LINQ to SharePoint in Kombination mit SPMetal erleichtert den vollständigen Zugriff auf SharePoint-Listendaten erheblich. Jedoch entsteht auch ein gewisser Mehraufwand, und bei Schemaänderungen müssen die automatisch erstellten Klassen neu generiert werden. Die Entscheidung, welcher Weg für den Listenzugriff beschritten werden soll, bleibt jedem selbst überlassen. Jedoch sollten die Zugriffe auf eine Liste zentral innerhalb einer Klasse gekapselt werden.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -