Updates in SharePoint

21. Juni 2012Malte Clasen

Eines der häufigsten Probleme bei Änderungswünschen an bereits produktiv eingesetzten SharePoint-Projekten ist der komplexe Update-Mechanismus. Da gibt es zunächst WebParts, bestehend aus C#-Code und den zugehörigen .webpart-Dateien. Diese greifen auf in XML deklarierte Listen zu, deren Struktur wiederum über in XML deklarierte ContentTypes definiert wird, die ihrerseits auf in einer dritten XML-Datei definierten SiteColumns aufbauen. Die Darstellung wird über CSS-Dateien formatiert, die Änderungen über C#-Workflows gesteuert. Aktiviert wird das ganze über ein in XML spezifiziertes Feature. Und nun steht das erste Update ins Haus.

SharePoint stellt mit dem Powershell-Commandlet Update-SPSolution einen dem Namen nach passenden Befehl zur Verfügung. Kann man auf Anhieb sagen, wie dieser sich auf die zuvor genannten Elemente auswirkt? Die erste Überraschung ist, dass zusätzliche Features in einer WSP-Datei schlichtweg ignoriert werden. Das führt schnell dazu, dass Entwickler Solutions mit allgemeinen Features ausstatten, beispielsweise „Site“ und „Web“ benannt, die dann für alle zu diesem Scope gehörigen Elemente verwendet werden. Aus fachlicher Sicht wäre eine klarere Benennung wünschenswert. Das weniger offensichtliche, aber weitaus größere Problem ist die Frage, was mit den vorhandenen Datenstrukturen geschieht. Wenn man im Update ein Feld des ContentTypes umbenennt, wird dann dieses Feld auch von Update-SPSolution umbenannt? Oder das alte gelöscht und das neue angelegt? Oder passiert gar nichts? Selbst wenn man diese Punkte alle einzeln recherchieren kann, so stößt man als Entwickler immer wieder auf Unklarheiten und im Zweifel unerwünschtes Verhalten. Retract und Deploy sind übrigens auch keine Alternative, da dabei alle bereits in den Listen vorhandenen Daten verloren gehen.

Updates in SQL

SharePoint ist nicht die einzige Plattform, die mit veränderlichen Datenstrukturen umgehen muss. Ein Blick auf SQL-Datenbanken hilft weiter. SharePoint ist zwar kein relationales Datenbanksystem, aber die Ablage von Daten in SharePoint-Listen und die in SQL-Tabellen sind sich hinreichend ähnlich, um sich von dort inspirieren lassen zu können. Dabei ist zu beachten, dass man in SharePoint nur im Objektmodell eine zu SQL vergleichbare Flexibilität hat: Die Methoden von SPList und die Parameter von ALTER TABLE entsprechen sich in weiten Teilen, während die Elements.xml nicht über die Möglichkeiten eines einmaligen CREATE TABLE hinausgeht. Für SQL existieren Werkzeuge wie LiquiBase und dbDeploy, die Struktur-Updates vornehmen können. Das zugrunde liegende Konzept sind ChangeSets, d. h. Änderungen von einer Version zur nächsten. Diese Änderungen können nacheinander auf die zunächst leere Datenbank angewandt werden und ergeben dann die gewünschte Struktur. Bereits vorhandene Datenbanken können entsprechend aktualisiert werden: Wenn die Datenbank bereits in Version 67 vorliegt, und die ChangeSets bis Version 83 reichen, werden eben nur 68 bis 83 angewandt. Als Entwickler spezifizieren sie also nicht, wie die neue Struktur aussehen soll, sondern welche Änderungen an der vorherigen Version notwendig sind, um zur neuen zu gelangen. Damit ist dann auch direkt klar, was mit den vorhandenen Daten geschieht: Wenn Sie Feld A löschen und Feld B anlegen, werden die Daten verworfen. Wenn Sie A und B umbenennen, bleiben sie erhalten.

Implementierung in SharePoint

Dieses Konzept lässt sich direkt im SharePoint-Objektmodell umsetzen. Dazu bietet es sich an, parallel zu den direkten Standard-Methoden von SPList etc. Extension-Methods mit ChangeSet-Funktionalität bereitzustellen:

SPList list;

SPContentType contentType;
list.Edit(0).AddContentType(contentType).Apply();

Edit(0) prüft zunächst, ob die Liste derzeit in Version 0 vorliegt. Wenn dies der Fall ist, werden die nachfolgenden Änderungen, hier das Hinzufügen eines ContentTypes, beim Aufruf von Apply() ausgeführt und die Version eins hochgezählt (Listing 1). Wird derselbe Code später erneut ausgeführt, ignoriert Apply() das ChangeSet. Änderungen werden entsprechend inkrementell hinzugefügt, auch wenn sie vorherige Änderungen rückgängig machen:

list.Edit(0).AddContentType(contentType).Apply();
list.Edit(1).RemoveChildContentTypesOf(contentType).Apply();

Auch wenn hier bei neuen Deployments doppelte Arbeit geleistet wird, kann man nur so sichergehen, dass bestehende Deployments am Ende auf demselben Stand sind.

Listing 1
using System;
using System.Collections.Generic;
public abstract class ChangeSet<T> : IChangeSet

{

private readonly List<Action<T>> _changes = new List<Action<T>>();
private readonly T _entity;
private readonly int _fromVersion;
private readonly string _contextId;
protected ChangeSet(T entity, int fromVersion, string contextId)
{

_entity = entity;
_fromVersion = fromVersion;
 _contextId = contextId;

}

protected ChangeSet(ChangeSet<T> changeSet)

{

_entity = changeSet._entity;
_fromVersion = changeSet._fromVersion;
_contextId = changeSet._contextId;
_changes = new List<Action<T>>(changeSet._changes); 

}

protected ChangeSet(ChangeSet<T> changeSet, Action<T> change) : this(changeSet) 

{

_changes.Add(change), 

}

protected abstract Uri WebUrl { get; }
protected T Entity { get { return _entity; } }

public void Apply()

{

var versionAccess = new VersionAccess(WebUrl);
var entityVersion = versionAccess.GetVersion(_entity, _contextId);
if (entityVersion < _fromVersion)
t
hrow new MissingChangeSetsException(_entity, entityVersion, _fromVersion, _contextId);
if (entityVersion > _fromVersion)
return;


foreach (var change in _changes)
change(_entity);
OnPostChanges(),
versionAccess.SetVersion(_entity, _fromVersion + 1, _contextId)

}

 protected abstract void OnPostChanges();

}

Die zugehörigen Versionsnummern können Sie in einer versteckten Liste speichern. Da bei SharePoint-Projekten meist keine zentrale Datenabstraktionsschicht vorhanden ist, empfiehlt es sich, die Versionen mit Kontext zu speichern:

list.Edit(0, contextId).AddContentType(contentType).Apply();

Wenn der Urlaubskalender dem RootWeb von Version U1 zu U2 einen neuen ContentType hinzufügt, und das Wetter-WebPart dies für W1 zu W2 erledigt, ist es egal, in welcher Reihenfolge Sie diese Funktionen später aktivieren. Bei einem globalen Versionszähler hingegen wäre die festgelegte Reihenfolge an sich unabhängiger Elemente eine überraschende Einschränkung.

Die so definierten inkrementellen Updates können über Feature-EventReceiver ausgeführt werden. So entstehen die Strukturen zum selben Zeitpunkt, wie man es auch von deklarativen Strukturen kennt.

Haben Sie Fragen zu dem komplexen Update-Mechanismus in SharePoint oder zu bestimmten Komponenten? Gerne möchte ich mich mit Ihnen zu Erfahrungen in diesem Bereich austauschen. Ich freue mich auf Ihre Kommentare.

Unter folgendem Link finden Sie Verweise auf darauf aufbauende Konzepte: http://malteclasen.de/blog/index.php/2012/05/sharepoint-updating-data-structures. Darüber hinaus ist heute ein weiterführender Artikel zum Thema agile SharePoint-Entwicklung heute im dotnetpro-Magazin erschienen: http://www.dotnetpro.de/articles/onlinearticle4160.aspx.

Malte Clasen Malte Clasen ist promovierter Software-Ingenieur und seit über 10 Jahren als Entwickler tätig. Bei adesso arbeitet er als Senior Software Engineer. Sein Schwerpunkt liegt auf agiler Software-Entwicklung mit .NET.
Artikel bewerten:
1 Star2 Stars3 Stars4 Stars5 Stars
Loading...

Kommentar hinzufügen:

Ihr Kommentar: