Datenzugriff in SharePoint: Hierarchical Object Store 30.07.2012

Markus Schwamberger
Markus Schwamberger, Senior eXpert
Dies ist der vierte Teil einer Serie, die sich mit der Frage beschäftigt auf welche Arten Konfigurationen in Applikationen unter SharePoint gespeichert werden können. Es wird zuerst immer kurz die Methode vorgestellt, gefolgt von einem kurzen Beispiel und den Vor- und Nachteilen.

Eine vollständige Übersicht über bisherige und noch folgende Artikel ist hier zu finden.

Im letzten Teil wurde das SPPropertyBag vorgestellt. Der Nachteil des Property Bags ist, das nur string, int oder DateTime gespeichert werden können. Ist es dagegen notwendig komplexere Objekte zu speichern, müssen diese per Hand serialisiert und dann als string gespeichert werden. Wem das zu umständlich ist kann alternativ den Hierarchical Object Store verwenden. Dieser funktioniert ähnlich einem Propery Bag auf Farm-Ebene und ermöglicht das Speichern und Auslesen ganzer Objekte.

Genau wie das Property Bag ist der Hierarchical Object Store eine Grundfunktion von SharePoint und muss nicht separat installiert oder aktiviert werden. Das bedeutet jede Applikation kann auf diesen zugreifen.

Um ein Objekt im Hierarchical Object Store zu speichern, muss dieses von SPPersistedObject ableiten. Alle Felder (nicht die Properties!), welche mit dem [Persisted] Attribut gekennzeichneten sind, werden als XML serialisiert und in der SharePoint Konfigurations-Datenbank abgelegt. Das serialisierte Objekt steht dann farmweit allen Applikationen zur Verfügung.

Beispiel

Als Beispiel erstellen wir ein einfaches Objekt zum Speichern von Benutzername und Passwort. Das Objekt leitet von SPPersistedObject ab und enthält zwei Felder, welche mit dem Persisted Attribut gekennzeichnet werden. (Um das Code-Beispiel übersichtlich zu halten sparen wir uns die Properties)

public class MyCredentials : SPPersistedObject
{
    [Persisted]
    private string _connectionString;
 
 
    public string ConnectionString
    {
        get { return this._connectionString; }
        set { this._connectionString = value; }
    }
 
    public MyCredentials() { }
 
    public MyCredentials(string name, SPPersistedObject parent, Guid id)
        : base(name, parent, id)
    { }
 
    protected override bool HasAdditionalUpdateAccess()
    {
        return true;
    }
}

Nun brauchen wir noch eine Methode zum Lesen und eine zum Schreiben des Objektes:

/// <summary>
/// Liest ein Objekt aus dem Hierarchical Object Store aus.
/// Ist das Objekt noch nicht vorhanden, wird es erstellt
/// </summary>
public static MyCredentials GetObject(SPWebApplication webApplication, string name)
{
    // Mit der GetChild Methode von SPWebApplication 
    // können die persistierten Objekte wieder ausgelesen werden
    var credentials = webApplication.GetChild<MyCredentials>(name); 
    if (credentials == null)
    {
        // Neues Objekt erstellen, falls noch nicht vorhanden
        return CreateNew(webApplication, name);
    }
    return credentials;
}
 
/// <summary>
/// Persistiert das Objekt im Hierarchical Object Store 
/// </summary>
public static MyCredentials CreateNew(SPWebApplication webApplication, string name)
{
    return new MyCredentials(name, webApplication, Guid.NewGuid());
}

Zum Testen erstellen wir wieder ein WebPart, welches fast identisch mit dem letzten Beispiel ist:

[ToolboxItemAttribute(false)]
public class WebPartMitHierarchicalObjectStore : WebPart
{
    private Button _buttonSave;
    private Button _buttonLoad;
    private TextBox _textBoxConnectionString;
 
    const string PersistedObjectName = "DemoKey";
 
 
    protected override void CreateChildControls()
    {
        _buttonSave = new Button
        {
            Text = "Save data!",
            ID = "ButtonSave"
        };
 
        _buttonLoad = new Button
        {
            Text = "Load data!",
            ID = "ButtonLoad"
        };
 
        this._textBoxConnectionString = new TextBox();
 
        _buttonSave.Click += PropertySpeichern;
        _buttonLoad.Click += PropertyLaden;
 
        Controls.Add(_buttonSave);
        Controls.Add(_buttonLoad);
        Controls.Add(_textBoxConnectionString);
    }
 
    private void PropertyLaden(object sender, EventArgs e)
    {
        var web = SPContext.Current.Site.WebApplication;
        var credentials = MyCredentials.GetObject(web, PersistedObjectName);
 
        _textBoxConnectionString.Text = credentials.ConnectionString;
    }
 
 
    private void PropertySpeichern(object sender, EventArgs e)
    {
        var webApplication = SPContext.Current.Site.WebApplication;
 
        var credentials = MyCredentials.GetObject(webApplication, PersistedObjectName);
        credentials.ConnectionString = _textBoxConnectionString.Text;
        credentials.Update();
 
        _textBoxConnectionString.Text = string.Empty;
    }
}

Mehr ist auch bei dieser Variante nicht notwendig um Einstellungen zu speichern und laden.

Fazit

Der Hierarchical Object Store ist dem im letzten Artikel vorgestellten Property Bag sehr Ähnlich. Trotzdem unterscheidet es sich in in ein paar Grundlegenden Dingen, was sich in den Vor- und Nachteilen dieser Methode äussert:

Vorteile

  • Implementierung
    Gut geeignet für komplexe Datentypen. Diese müssen allerdings von SPPersistedObject ableiten, was die Einsatzmöglichkeiten limitiert.
  • Wartbarkeit
    Standardfeature von SharePoint. Es müssen keine zusätzlichen Komponenten installiert werden und nichts konfiguriert werden. Im Gegensatz zum Property Bag werden Werte aber nicht automatisch zusammen mit dem Feature entfernt. Es können also Einträge in der Datenbank zurück bleiben!

Nachteile

  • Administration
    Es gibt keine mitgelieferte Oberfläche zum Verwalten der Werte. Diese muss selbst erstellt werden. Ebenso existiert keine Möglichkeit für Versionierung oder Genehmigungsprozesse. Eine Änderung ist also sofort „live“.
  • Sicherheit Nicht geeignet um sensible Informationen zu speichern. Zum Schreiben sind außerdem Farmadministrator Rechte notwendig.
  • Scope Im Gegensatz zum Property Bag werdeb Werte im Hierarchical Object Store immer auf Farm Ebene gespeichert.
Share |

Verwendung des tabellarischen Modells als Datenquelle für Reporting Services 27.07.2012

Sylvio Hellmann
Sylvio Hellmann, Manager BI Services/Chief eXpert

Neu in Microsoft SQL Server 2012 ist das BI-Semantikmodell (BI Semantic Model, BISM) für Analysis Services. Von diesem BISM gibt es zwei unterschiedliche Implementierungen: das Tabellarische (tabulare) und das Multidimensionale (multidimensional) Modell.

Das multidimensionale Modell verwendet das Konzept von Cubes und Dimensionen, die auf einer OLAP-Engine basieren. Dieses Modell wurde vom Unified Dimensional Model (UDM) abgeleitet, dass in früheren Versionen der Analysis Services (vor 2012) zum Einsatz kam. Die Abfragesprache für dieses Modell ist die Multi Dimensional eXpression (MDX). Für den Zugriff auf dieses Modell steht in Reporting Services 2012 eine entsprechende Dataextension zur Verfügung – Microsoft SQL Server Analysis Services.

Das tabellarische Modell baut auf dem Konzept von Tabellen und Beziehungen auf, die auf einer “In-Memory”-Engine, mit dem Namen xVelocity (vorher VertiPaq), basieren. Dabei werden die Daten für dieses Modell im Arbeitsspeicher gehalten und dadurch ist der Zugriff darauf wesentlich schneller, als ein Zugriff auf ein multidimensionales Modell (OLAP). Die Abfragesprache für das tabellarische Modell ist die Data Analysis eXpression (DAX), die aus Excel-Formeln abgeleitet wurde. Standardmäßig wird Reporting Services im nativen Modus jedoch mit keiner speziellen Datenerweiterung ausgeliefert, die es ermöglicht, auf das tabellarische Modell per DAX zuzugreifen. So ist nur ein Zugriff über MDX mit der Dataextension “Microsoft SQL Server Analysis Services” möglich. Dies hat jedoch einige Nachteile in Hinblick auf Usability und Performance.

Bei der Analyse von Power View-Berichten bin ich zufällig darauf gestoßen, dass in diesen Berichten ein Datenquellentyp “DAX” verwendet wird. Nachdem ich mir die verfügbaren Datenerweiterungen in SharePoint angesehen habe, konnte ich sehen, dass dort die Datenerweiterung “DAX” registriert ist. Nun habe ich mir gesagt, wenn der Datenquellentyp im integrierten SharePoint-Modus der Reporting Services funktioniert, gibt es eigentlich keinen Grund, warum dies nicht auch im nativen Modus funktioniert. Also habe ich dies ausprobiert!

Dieser Artikel soll Ihnen zeigen, wie Sie den Zugriff auf Analysis Services im tabellarischem Modell mit DAX umsetzen können.

Damit Sie innerhalb von Reporting Services einen neuen Datenquellentyp verwenden können, müssen Sie diesen auf Ihrer Umgebung zuerst registrieren. Diese Registrierung muss einerseits für Visual Studio auf dem Entwicklercomputer erfolgen (damit Sie entsprechende Datenquellen anlegen können) und andererseits muss dieser Datenquellentyp auf dem Berichtsserver bekannt gemacht werden (damit Sie Berichte verwenden können, die auf diesen Datenquellentyp basieren). Bevor Sie Änderungen an den folgenden Konfigurationsdateien vornehmen, sollten Sie Sicherungskopien erstellen – man kann ja nie wissen.

  • Registrierung der Datenerweiterung in Visual Studio 2010:
    Öffnen Sie die Datei "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\RSReportDesigner.config" mit dem Texteditor und suchen darin das Tag <DATA> fügen Sie unterhalb dieses Tags folgende Zeile ein:
    <Extension Name="DAX" Type="Microsoft.ReportingServices.DataExtensions.AdoMdDaxConnection,Microsoft.ReportingServices.DataExtensions"/>
  • Registrierung der Datenerweiterung in Reporting Services 2012:
    Öffnen Sie die Datei "C:\Program Files\Microsoft SQL Server\MSRS11.MSSQLSERVER\Reporting Services\ReportServer\rsreportserver.config" mit dem Texteditor und suchen darin das Tag <DATA> fügen Sie unterhalb dieses Tags folgende Zeile ein:
    <Extension Name="DAX" Type="Microsoft.ReportingServices.DataExtensions.AdoMdDaxConnection,Microsoft.ReportingServices.DataExtensions"/>

Nachdem Sie die Anpassungen an den Konfigurationsdateien vorgenommen haben, starten Sie die SQL Server Data Tools (mit BI-Erweiterung) und erstellen ein neues Reporting Service-Projekt. Legen Sie eine neue freigegebene Datenquelle an. Als Datenquellentyp wählen Sie das “Microsoft BI-Semantikmodell für Power View” aus:

clip_image002

Sollte dieser Datenquellentyp nicht vorhanden sind, muss bei der Registrierung in der Visual Studio-Konfigurationsdatei etwas schief gegangen sein. Kontrollieren Sie deshalb die Konfigurationsdatei und starten Visual Studio neu.

Als Verbindungszeichenfolge verwenden Sie folgende Zeichenfolge:
“Data Source=<Servername>[\<Instancename>];Initial Catalog=<Datenbankname>

Zum Bearbeiten der Verbindungszeichenfolge steht Ihnen leider kein Assistent zur Verfügung, sodass Sie diesen Text nicht automatisch generieren lassen können.

Wenn Sie das BI-Semantikmodell in SharePoint abgelegt haben, müssen Sie als Data Source die entsprechende URL zu dem Modell in SharePoint eintragen. Das sieht dann so aus:
Data Source=/sites//.xlsx">/sites//.xlsx">http://<sharePoint server>/sites/<pfad>/<Name des BISM-Modells>.xlsx;

Unter Anmeldeinformationen wählen Sie „Windows Authentifizierung (integrierte Sicherheit)“ aus.

Danach können Sie ein neues Dataset erstellen. In dem folgenden Beispiel handelt es sich um ein freigegebenes Dataset, das damit auch berichtsübergreifend zur Verfügung steht.

clip_image004

Verwenden Sie für das Dataset die Datenquelle, die Sie vorher definiert haben. Als Abfrage (vom Typ Text) können Sie jetzt Ihre DAX-Abfrage eintragen. In der aktuellen Version steht Ihnen für die DAX-Abfrage leider kein Programm zur Verfügung, dass Sie bei der Erstellung der Abfrage unterstützt. Doch bei Interesse können Sie sich an die SDX AG wenden – wir unterstützen Sie gerne.

Eine Alternative zu dem oben beschriebenen Weg stellt der Vorschlag von Chris Webb dar (http://cwebbbi.wordpress.com/2011/09/06/detail-level-reporting-with-dax/). In diesem Beispiel verwendet er die Standard Analysis Services Datenerweiterung. Darin benutzt er eine DMX-Abfrage, um eine DAX-Abfrage abzusetzen. Sicherlich nicht schön, aber auch dieser Weg funktioniert.

Nachdem Sie die Dataset erstellt haben, können Sie dann basierend auf dieses Dataset einen neuen Bericht erstellen. Dort stehen Ihnen die gleichen Möglichkeiten zur Verfügung wie mit jeder anderen Datenquelle.
Die Abfrage ist nur ein Beispiel (und nur ein kleiner Teil davon) aus meinem Testbereich. Wenn Sie mehr Informationen zu der Abfrage-Syntax der DAX wissen möchten, finden Sie bei Microsoft einige Informationen unter http://msdn.microsoft.com/de-de/library/gg492201
Leider fasst sich Microsoft sehr kurz mit der Beschreibung dieser Syntax und geizt mit Beispielen, deshalb habe ich mich entschlossen in einem der nächsten Flurfunkeinträge dieses Thema genauer zu erklären.

Fazit

Die Verwendung von DAX innerhalb von Reporting Services ist sehr einfach – ein wenig Konfiguration und schon kann es losgehen. Sollten Sie noch Fragen zu diesem Thema haben, stehen meine Kollegen und ich Ihnen gerne zur Verfügung.

Share |

Bewegungsdrang (Teil 2): Hello (Kinect) World! 25.07.2012

Sven Willsch
Sven Willsch, Chief eXpert

Es ist Donnerstag Vormittag und mein Smartphone vibriert lautlos in der rechten Innentasche meines Jackets. Dem Display entnehme ich nur die Worte "Dein Packet ist da ;)". Das dezente Glückgefühl, welches mich beschleicht, führt zu einem leichten aber doch sichtbaren Grinsen in meinem Gesicht. Die Kinect für Windows ist da und gleichzeitig mit ihr auch der Tüftler und Bastler in mir.

Während ich mich in meinem ersten Artikel "Der Kinect Controller" noch auf Internetrecherche beschränken musste, bin ich nun in der komfortablen Lage erste Praxiserfahrungen zu sammeln. Was also bleibt mir übrig, als meinen Arbeitstag pünktlich zu beenden und schon mal vorweg einen langen Abend einzuplanen?

Software und Installation

Der Controller ist ausgepackt, alle Teile des Kartons liegen in der Ecke des Raumes verteilt und wer kennt ihn nicht, diesen tiefen Wunsch der einen Tekkie beim Anblick eines neuen USB Geräts direkt neben dem heimischen Rechner beschleicht? Tun Sie es nicht! Windows findet hierzu keine Treiber und nach der Installation sind Sie nur stolzer Besitzer eines etwa 200€ teurem generischen USB Hubs.

Sparen Sie sich also die Arbeit ein nicht erkanntes Gerät über den Gerätemanager zu entfernen und halten Sie sich an die Release Notes, die man als gestandener Entwickler gerne mal überliest. Und in denen heißt es "Make sure the Kinect sensor is not plugged into the USB port on the computer prior to running setup".

Eventuellen Komplikationen zum Trotz (und mitunter schweren Herzens) ist es also erst mal sinnvoll die folgenden Downloads zu tätigen:

Dabei will ich es auch vorerst belassen, denn primär geht es in dem vorliegenden Artikel um die Installation und Inbetriebnahme und nicht darum ein perfektes Toolset unser eigen nennen zu dürfen. Ist zumindest das SDK installiert kommt schließlich der große Moment, die Kinect gibt ein erstes Lebenszeichen von sich nachdem ein weiterer USB Port des Laptops seine Bestimmung findet.

Kurz noch einen Blick in den Geräte-Manager ob alles korrekt installiert ist

und schon können wir mit einem Klick auf den in der Gruppe Kinect for Windows SDK 1.5 befindlichen Developer Toolkit Browser nach der wohl wichtigsten Demo-Applikation suchen.

Startet man hier den Kinect Explorer so strahlt Ihnen schließlich (wie mir übrigens auch) das Antlitz eines völlig übermüdeten Entwicklers entgegen, mit dem die Leidenschaft zu später Stunde mal wieder durchgegangen ist.

Einen besonderen Hinweis gebe ich an dieser Stelle noch allen Lesern die ähnlich ungeduldig waren wie ich und das Developer Toolkit in der Version 1.5.0 vorab installiert haben. Ihre Kinect ist nicht kaputt, nein, die Entwickler haben sich nur den Spaß gemacht ein kleines Globalisierungs bzw. Cultureproblem einzubauen.

Dies hat den netten Nebeneffekt, dass bei der Konvertierung von Strings in Fließkommazahlen fast alle Beispielapplikationen abstürzen, weil selbige in unserem Sprachraum nun mal mit Komma anstatt Punkt getrennt werden. Stellen Sie in diesem Fall also einfach sicher, dass Sie das Update 1.5.1 vom Juni nachinstallieren.

Ausblick

Im nächsten Teil dieser Blogreihe werde ich auf die Verarbeitung der Streams eingehen. Sie werden dort lesen wie RGB Kamera und Tiefensensor in einer WPF Anwendung auf einfachste Art und Weise genutzt werden können.

Ich würde mich freuen, wenn Sie auch dann wieder die Lust verspüren mir bei meinem Bewegungsdrang zu folgen.

Share |

Datenzugriff in SharePoint: Property Bag 23.07.2012

Markus Schwamberger
Markus Schwamberger, Senior eXpert

Dies ist der dritte Teil einer Serie, die sich mit der Frage beschäftigt auf welche Arten Konfigurationen in Applikationen unter SharePoint gespeichert werden können. Es wird zuerst immer kurz die Methode vorgestellt, gefolgt von einem kurzen Beispiel und den Vor- und Nachteilen.

Eine vollständige Übersicht über bisherige und noch folgende Artikel ist hier zu finden.

Die zweite Teil dieser Serie beschäftigt sich mit der Möglichkeit Konfigurationswerte in einem Property Bag abzulegen.

Das Property Bag ist ein Hashtable zum Ablegen von key/value Paaren. SharePoint verwendet das Property Bag selbst um z.B. die Versionsnummer oder auch die Verwendung von SharePoint Designer zu speichern.

Je nach Ebene auf der man sich befindet gibt es ein eigenes Property Bag. Es ist also möglich Werte für die gesamte SharePoint Farm oder nur innerhalb einer Site zu speichern. Wichtig ist, dass der verwendete Key für die jeweilige Ebene eindeutig sein muss. Die folgende Tabelle fasst die Möglichkeiten zusammen:

Ebene Zugriff
SPFarm Die Properties Eigenschaft der SPPersistedObject Klasse.
SPWebApplication Die Properties Eigenschaft welche von SPPersistedObject abgeleitet wurde.
SPSite Die AllProperties Eigenschaft des Root Web.
SPWeb Die AllProperties Eigenschaft.
SPList Die RootFolder.Properties Eigenschaft.

Beispiel

Als einfaches Beispiel erstellen wir nun ein neues WebPart mit jeweils einem Button zum Speichern und einem Button zum Laden eines Properties.

[ToolboxItemAttribute(false)]
public class WebPartPropertyBag : WebPart
{
private Button _buttonSave;
private Button _buttonLoad;
private TextBox _textBoxConnectionString;
 
const string _key = "DemoKey";
 
/// <summary>Der Schlüssel wird von SharePoint automatisch klein geschrieben</summary>
public string Key { get { return _key.ToLower(); } }
 
protected override void CreateChildControls()
{
_buttonSave = new Button
{
Text = "Save data!",
ID = "ButtonSave"
};
 
_buttonLoad = new Button
{
Text = "Load data!",
ID = "ButtonLoad"
};
 
_textBoxConnectionString = new TextBox();
 
_buttonSave.Click += PropertySpeichern;
_buttonLoad.Click += PropertyLaden;
 
Controls.Add(_buttonSave);
Controls.Add(_buttonLoad);
Controls.Add(_textBoxConnectionString);
}
}

Nun benötigen wir eine Methode zum Speichern des Wertes:

private void PropertySpeichern(object sender, EventArgs e)
{
var web = SPContext.Current.Web;
web.Properties.Add(Key, _textBoxConnectionString.Text);
// Update MUSS aufgerufen werden wenn eine Property hinzugefügt oder geändert wurde
web.Update();
web.Properties.Update();
 
_textBoxConnectionString.Text = string.Empty;
}

Und zum Abschluss noch eine Methode zum Laden des Wertes:

private void PropertyLaden(object sender, EventArgs e)
{
var web = SPContext.Current.Web;
_textBoxConnectionString.Text = web.Properties[Key] ?? "Property nicht gefunden :(";        
}

Mehr Code ist nicht notwendig um auf das PropertyBag zuzugreifen! Um die so gespeicherten Werte zu verwalten ist allerdings noch eine separate Oberfläche notwendig. Mit ein wenig Aufwand ist es so möglich eine gut integrierte Verwaltung für mehrere Applikationen zu schreiben.

Fazit

Die Flexibilität des PropertyBags ist sowohl ein Vor- als auch ein Nachteil. Zum einen ist es möglich von verschiedenen Applikationen aus das gleiche Property zu verwenden. Auf der anderen Seite muss man aber auch aufpassen, keine Properties von anderen Applikationen oder gar SharePoint zu überschreiben. Deshalb empfiehlt es sich immer einen Präfix wie den Namen des Features zu verwenden.

Zum Abschluss noch einmal die Vor- und Nachteile des Property Bags.

Vorteile

  • Scope
    Werte können granular auf verschiedenen Ebenen gespeichert werden.
  • Implementierung
    Gut geeignet wenn einfache key/value Paare gespeichert werden sollen. Wobei hier zwingend auf eine eindeutige Namenskonvention geachtet werden muss, sonst überschreiben Applikationen ungewollt die Werte anderer Applikationen.

Nachteile

  • Implementierung
    Nicht so gut geeignet wenn es darum geht komplexe Datentypen zu speichern. Es können nur serialisierbare Datentypen gespeichert werden und diese müssen dazu noch „von Hand“ serialisiert werden.
  • Administration
    Es gibt keine mitgelieferte Oberfläche zum Verwalten der Werte. Diese muss selbst erstellt werden.
  • Sicherheit Zum schreiben ist je nach Ebene ist eine andere Berechtigung erforderlich (SPWebApplication nur als Farm Administrator). Lesen kann hingegen jeder Benutzer.
  • Wartbarkeit
    Standardfeature von SharePoint. Es müssen keine zusätzlichen Komponenten installiert und nichts konfiguriert werden. Zusätzlich müssen die Properties auch nicht verwaltet werden. Wird die Site gelöscht, werden auch die dazugehörigen Properties gelöscht.

Ein interessanter Hinweis noch zum Schluss. Auf das PropertyBag kann auch aus dem SharePoint Designer heraus zugegriffen werden.

Zu finden unter Websiteoptionen im RibbonMenu:

201

202

Innerhalb dieser Auflistung werden alle aktuell genutzten PropertyBag-Key-Value-Paare angezeigt.

Share |

Konfigurationsmöglichkeiten in SSIS 2012 20.07.2012

Andrej Kuklin
Andrej Kuklin, Senior BI eXpert

SQL Server Integration Services (SSIS) 2012 enthält mehrere Verbesserungen im Bereich Konfiguration, die dafür sorgen, dass man erstens weniger konfigurieren muss und zweitens die Konfiguration viel einfacher gestalten und für unterschiedliche Umgebungen anpassen kann. Das alte Konzept von Paketkonfigurationen wird zwar weiterhin im Package Deployment Model unterstützt, die neuen Parametrisierungsmöglichkeiten vom Project Deployment Model bieten aber viel mehr Entwicklungskomfort.

Zwei wichtige Neuerungen in SSIS 2012 sind besonders zu erwähnen: Project Connection Managers und der veränderte Execute Package Task.

Mit einem Project Connection Manager hat man endlich die Möglichkeit, Verbindungen zu Datenquellen zu definieren, die in mehreren Paketen verwendet werden können. Einen Project Connection Manager kann man leicht in einen Package Connection Manager konvertieren (und umgekehrt).

Project Connection Manager

Die Connection Manager sind in der RTM-Version von SSIS 2012 auch parametrisierbar (die ersten CTPs haben das noch nicht unterstützt), so dass man leicht und an einer Stelle die Verbindungsinformationen überschreiben kann.

Der neue Execute Package Task braucht keine eigenen Connection Manager mehr. Die Unterpakete können über eine Projektreferenz ausgeführt werden, was eine separate Konfiguration erübrigt.

Execute Package Task

 

Projekt- und Paketparameter

Richtig interessant wird es bei der Konfiguration von externen Abhängigkeiten mit Hilfe von Projekt- bzw. Paketparametern. Die Paketparameter haben eine gewisse Ähnlichkeit zu Variablen, die Projektparameter werden entsprechend paketübergreifend (auf der Projektebene) definiert. Genau wie mit Variablen, kann man mit Parametern abgeleitete Ausdrücke definieren.

Projektparameter

Parameterbasierte Ausdruecke

Die Parameter bieten in Vergleich zu Variablen ein paar zusätzliche Eigenschaften:

  • Sensitive – der Wert wird für im SSISDB Katalog bereitgestellte Projekte verschlüsselt
  • Required  – für die im SSIS Katalog bereitgestellte Projekte muss bei der Ausführung entweder ein Server Default Wert oder ein Execution Wert angeben werden (Matt Mannson erwähnt ein paar Situationen, wenn eine fehlende Konfiguration zu schwierig identifizierbaren Bugs führen kann)

Für die lokale Entwicklung kann man mehrere Parametereinstellungen im SSDT mit Hilfe von Konfigurationen zusammenfassen.

Projektkonfigurationen

Leider werden die Konfigurationen in der Projekt-Datei (.dtproj) gespeichert, was die Entwicklung auf unterschiedlichen Rechnern weniger komfortabel macht (dasselbe Problem hat man auch seit geraumer Zeit mit den Datenquellenkonfigurationen in SSAS-Projekten).

 

SSISDB Katalog

Die SSISDB-Umgebung für bereitgestellte Pakete kann wesentlich mehr. Nachdem man ein SSIS-Projekt in einem SSISDB-Katalog  bereitgestellt hat, hat man die Möglichkeit mehrere Umgebungen (Environments) zu definieren

SSISDB Umgebungen

Wie der Name schon vermuten lässt, enthalten die Umgebungen eine oder mehrere Umgebungsvariablen, die für die in einem SSIS-Projekt definierten Parameter verwendet werden können.

Umgebungsvariablen

Die angelegten Umgebungen kann man in Projektkonfiguration referenzieren, die Umgebungsvariablen können dann den Projekt- und Paketparametern zugewiesen werden. Alternativ kann man die Ausführung auch mit einem Default Wert oder nur für die aktuelle Ausführung gültigen Wert starten.

Referenzierung Umgebungsvariablen

Die vorgenommene Konfiguration wird in der SSISDB Datenbank persistiert und kann über die Transact-SQL API gelesen und geändert werden.

Insgesamt machen die Konfigurationsmöglichkeiten in SSIS 2012 einen sehr guten Eindruck. Es gibt zwar ein paar Unschönheiten und kleine SSDT-bezogene Bugs, aber die neuen Funktionen reduzieren den Konfigurationsaufwand auf das notwendige Minimum.

Share |

Switch über Strings 18.07.2012

Alexander Jung
Alexander Jung, Chief eXpert

Die Diskussion in einem früheren Beitrag zum Thema string interning hat mich dazu gebracht, etwas angestaubtes Wissen zu hinterfragen und zu aktualisieren: Was macht eigentlich eine switch Anweisung mit Strings?

Oder genauer: Finden hier String-Vergleiche statt? Wird das optimiert? Habe ich hier ein potentielles Performanceproblem?

 

switch basics

Bevor wir zu Strings kommen etwas Grundlagenwissen zu switch über Integers.

Das C# switch wird vom Compiler in ein MSIL switch übersetzt. Wesentlicher Unterschied ist, dass die IL-Variante den switch-Wert als 0-basierten Index in eine Tabelle von Sprungadressen verwendet. Der C# Compiler muss also die case labels in das dicht besetzte Intervall [0..N[ übersetzen. Ist das bereits der Fall – was gerade auch bei einfachen Enumerationen gegeben ist – dann hat der Compiler hiermit keine Arbeit:

   1: public enum Company
   2: {
   3:     SDX,
   4:     Apple,
   5:     Microsoft,
   6:     Google
   7: }
   8:  
   9: public static int EnumSwitch(Company company)
  10: {
  11:     switch (company)
  12:     {
  13:         case Company.SDX: Console.WriteLine(":-)"); break;
  14:         case Company.Apple: Console.WriteLine("iPhone"); break;
  15:         case Company.Microsoft: Console.WriteLine("Windows Phone"); break;
  16:         case Company.Google: Console.WriteLine("Android"); break;
  17:         default: Console.WriteLine("Festnetz..."); break;
  18:     }
  19:  
  20:     return 0;
  21: }

Wird vom Compiler zu:

   1: .method public hidebysig static int32 EnumSwitch(valuetype TestStringSwitch.Program/Company company) cil managed
   2: {
   3:     .maxstack 1
   4:     .locals init (
   5:         [0] valuetype TestStringSwitch.Program/Company CS$0$0000)
   6:     L_0000: ldarg.0 
   7:     L_0001: stloc.0 
   8:     L_0002: ldloc.0 
   9:     L_0003: switch (L_001a, L_0026, L_0032, L_003e)
  10:     L_0018: br.s L_004a
  11:     L_001a: ldstr ":-)"
  12:     L_001f: call void [mscorlib]System.Console::WriteLine(string)
  13:     L_0024: br.s L_0054
  14:     L_0026: ldstr "iPhone"
  15:     L_002b: call void [mscorlib]System.Console::WriteLine(string)
  16:     L_0030: br.s L_0054
  17:     L_0032: ldstr "Windows Phone"
  18:     L_0037: call void [mscorlib]System.Console::WriteLine(string)
  19:     L_003c: br.s L_0054
  20:     L_003e: ldstr "Android"
  21:     L_0043: call void [mscorlib]System.Console::WriteLine(string)
  22:     L_0048: br.s L_0054
  23:     L_004a: ldstr "Festnetz..."
  24:     L_004f: call void [mscorlib]System.Console::WriteLine(string)
  25:     L_0054: ldc.i4.0 
  26:     L_0055: ret 
  27: }

Treten Lücken auf, so füllt der Compiler diese mit Sprungadressen zum default-Zweig oder zerlegt das Intervall in Teilintervalle. Details nachzulesen hier.

 

switch über String – .NET 1.x

Strings sind keine Integers, also kann das eben beschriebene Verfahren so nicht funktionieren. In .NET 1.x kam hier string interning zum Einsatz:

Das Prinzip ist einfach: Da als case labels nur literale Strings zulässig sind, müssen diese interned sein. Und string.IsInterned liefert nicht etwa ein bool zurück, sondern die Referenz auf den String. Der vom C# Compiler generierte IL Code muss also zunächst mit der switch-Variable string.IsInterned befragen. Bekommt er hier null, dann kann er gleich zum default-Zweig springen; andernfalls bekommt er die Referenz auf den selben(!) String und kann die einzelnen case labels sehr effizient über object.ReferenceEquals überprüfen.

Nachzulesen in Applied Microsoft Windows .NET Framework Programming, Jeffrey Richter, 2002 – einen passenden Link habe ich leider nicht gefunden.

Nicht gerade vergleichbar dem switch über Integers, aber bei weitem keine Kaskade von String-Vergleichen… .

 

switch über String – .NET 2.0

Mit .NET 2.0 führte Microsoft CompilerRelaxations.NoStringInterning ein, womit das gerade beschriebene Verfahren nicht mehr tragfähig war. Seither fährt der Compiler zweigleisig:

Für wenige case labels (in meinen Experimenten bis zu 6 Werte) werden tatsächlich string.Equals Aufrufe verwendet. Beispiel:

   1: public static int ShortSwitch(string company)
   2: {
   3:     switch (company)
   4:     {
   5:         case "SDX AG": Console.WriteLine(":-)"); break;
   6:         case "Apple": Console.WriteLine("iPhone"); break;
   7:         case "Microsoft": Console.WriteLine("Windows Phone"); break;
   8:         case "Google": Console.WriteLine("Android"); break;
   9:         case null: Console.WriteLine("kein Anschluss..."); break;
  10:         default: Console.WriteLine("Festnetz..."); break;
  11:     }
  12:     return 0;
  13: }

Wird laut Reflector zu:

   1: public static int ShortSwitch(string company)
   2: {
   3:     string CS$0$0000 = company;
   4:     if (CS$0$0000 == null)
   5:     {
   6:         Console.WriteLine("kein Anschluss...");
   7:     }
   8:     else if (!(CS$0$0000 == "SDX AG"))
   9:     {
  10:         if (CS$0$0000 == "Apple")
  11:         {
  12:             Console.WriteLine("iPhone");
  13:         }
  14:         else if (CS$0$0000 == "Microsoft")
  15:         {
  16:             Console.WriteLine("Windows Phone");
  17:         }
  18:         else if (CS$0$0000 == "Google")
  19:         {
  20:             Console.WriteLine("Android");
  21:         }
  22:         else
  23:         {
  24:             Console.WriteLine("Festnetz...");
  25:         }
  26:     }
  27:     else
  28:     {
  29:         Console.WriteLine(":-)");
  30:     }
  31:     return 0;
  32: }

Die Art in der die if-Anweisungen geschachtelt sind variiert je nach der Verwendung von null als case label und dem default-Zweig. Grundsätzlich kann man aber sagen, dass die Vergleiche in der Reihenfolge auftauchen, in der sie auch im Code stehen.

Wer Bedenken bzgl. Performance hat, mag das im Hinterkopf behalten. Persönlich halte ich das aber für unsinnige Mikrooptimierung an der falschen Stelle. Falls sich hier tatsächlich ein Performanceproblem ergibt (objektiv, d.h. durch Messungen belegt!), dann ist eher die Verwendung von switch über Strings generell das Problem, nicht die Reihenfolge der case labels.

Daher würde ich in aller Regel der Wartbarkeit und Lesbarkeit den Vorzug geben und die case labels nach inhaltlichen Kriterien ordnen.

 

Wenn mehr case labels zum Einsatz kommen wird ein lokales Dictionary verwendet, das den String in einen Index übersetzt.

Das folgende Beispiel unterscheidet sich vom letzten nur durch ein zusätzliches case label:

   1: public static int LongSwitch(string company)
   2: {
   3:     switch (company)
   4:     {
   5:         case "SDX AG": Console.WriteLine(":-)"); break;
   6:         case "Apple": Console.WriteLine("iPhone"); break;
   7:         case "Microsoft": Console.WriteLine("Windows Phone"); break;
   8:         case "Google": Console.WriteLine("Android"); break;
   9:         case "Nokia": Console.WriteLine("Symbian"); break;
  10:         case null: Console.WriteLine("kein Anschluss..."); break;
  11:         default: Console.WriteLine("Festnetz..."); break;
  12:     }
  13:     return 0;
  14: }

Aber der generierte Code sieht deutlich anders aus:

   1: public static int LongSwitch(string company)
   2: {
   3:     string CS$0$0000 = company;
   4:     if (CS$0$0000 != null)
   5:     {
   6:         int CS$0$0001;
   7:         if (<PrivateImplementationDetails>{A83D8BBE-00B9-48D6-9018-AC3F6C8BDF0B}.$$method0x6000004-1.TryGetValue(CS$0$0000, out CS$0$0001))
   8:         {
   9:             switch (CS$0$0001)
  10:             {
  11:                 case 0:
  12:                     Console.WriteLine(":-)");
  13:                     goto Label_00D9;
  14:  
  15:                 case 1:
  16:                     Console.WriteLine("iPhone");
  17:                     goto Label_00D9;
  18:  
  19:                 case 2:
  20:                     Console.WriteLine("Windows Phone");
  21:                     goto Label_00D9;
  22:  
  23:                 case 3:
  24:                     Console.WriteLine("Android");
  25:                     goto Label_00D9;
  26:  
  27:                 case 4:
  28:                     Console.WriteLine("Symbian");
  29:                     goto Label_00D9;
  30:             }
  31:         }
  32:     }
  33:     else
  34:     {
  35:         Console.WriteLine("kein Anschluss...");
  36:         goto Label_00D9;
  37:     }
  38:     Console.WriteLine("Festnetz...");
  39: Label_00D9:
  40:     return 0;
  41: }

Was in der C#-Darstellung vom Reflector nicht angezeigt wird (wohl aber wenn man auf IL umschaltet) ist die Initialisierung des Dictionary’s: Hier werden die Strings der case labels als Key eingetragen, Value ist ein fortlaufender Index.

Dieses Vorgehen erfordert zwar den Aufbau eines zusätzlichen Dictionary’s je switch Anweisung, ist aber in der Konsequenz effizienter als die alte Variante über string interning:

  • Das Dictionary arbeitet nicht anders als der intern pool, enthält aber genau die für die spezielle switch Anweisung relevanten Einträge. Je nachdem wie “voll” der intern pool ist ein relevanter Gewinn.
  • Nach dem Lookup im Dictionary ist kein Referenz-Vergleich mehr notwendig, stattdessen steht ein numerischer Index zur Verfügung mit dem auch auf IL-Ebene die switch Anweisung verwendet werden. Ebenfalls ein Effizienzgewinn.

 

Optimierung?

Dass ein switch über Strings mit mehr Aufwand verbunden ist dürfte klar sein. Man findet deshalb gelegentlich den Hinweis, hier Optimierung zu betreiben, üblicherweise indem der String zunächst in ein semantisch gleichwertiges Enum geparst wird (etwa in diesem Kommentar).

Im Allgemeinen kann ich die Verwendung von Enums nur empfehlen. Aber nicht wegen eventueller Performanceeffekte beim switch, sondern wegen der zusätzlichen Typ-Sicherheit und der Unterstützung bei Intellisense und Refactoring. In diesen Fällen sollte dann aber auch durchgängig die Enumeration – nicht der String – im Programm verwendet werden. Typisches Beispiel ist ein Eintrag in den appSettings, der nur bestimmte Werte annehmen darf.

Bei Fällen in denen das nicht möglich ist und das Parsing nur wegen der switch Anweisung durchgeführt würde macht die genannte Optimierung hingegen keine großen Sinn. Im Gegenteil: Parsing eines Enums ist aufwendiger als ein Lookup in einem Dictionary!

 

Fazit

Gründe switch über Strings zu verwenden kann es viele geben. Möglicherweise sind die Texte zwar vorgegeben, entsprechen aber nicht der C# Syntax; möglicherweise muss man eine offene Menge an Werten unterstützen; oder strenge Typisierung behindert die Versionierungsstrategie.

In aller Regel kann man sich aber darauf verlassen, dass der Compiler gute Arbeit bei einer möglichst effizienten Umsetzung leistet.

In Fällen in denen ein Enum eine Option ist sollte man diese Möglichkeit trotzdem nutzen. Aber nicht um vorgeblichen Nachteilen des switch über Strings entgegenzuwirken, sondern weil die Vorteile der Typsicherheit einfach stechen – übrigens auch bezüglich switch über Integers.

Share |