Links
Vorbemerkungen
Jeder, der sich ernsthaft mit service-orientierten Architekturen auseinander setzt, wird im .NET-Bereich früher oder später über die Stichworte Entitiy Framework (EF) und WCF stolpern. Dabei handelt es sich zwar um 2 unterschiedliche Technologien, das Zusammenspiel der beiden ist jedoch u.U. extrem wichtig. EF ist der objektrelationale Mapper (ORM), der letztlich den Zugriff auf in relationalen Datenbanken gespeicherten Daten kapselt und WCF ist die Technologie, um diese Daten per Service nach außen zugreifbar zu machen.
Das Szenario ist letztlich immer das gleiche. Irgendwo liegen Daten meist in Form relationaler Datenbanken herum. Diese muss man erst einmal programmatisch abgreifen und somit codeseitig verfügbar machen. Dazu sind in den vergangenen Jahren diverse Technologien entwickelt worden. Angefangen hat alles mit Direktzugriffen per ADO und später ADO.NET. Es gab bereits früh Techniken, um Datenbank-Inhalte weitgehend automatisiert zu laden und im Code verfügbar zu machen (z.B. typisierte Datasets). So richtig wollte das aber nicht performen und viele professionelle Anwender schreckten vor dem Einsatz dieser black boxes eher zurück.
Daten handeln
Die rasche Entwicklung der Programmiersprachen und Frameworks brachte dann die Möglichkeit, Code intelligenter werden zu lassen. Die Zeit der ORM brach an. Aus der Java-Welt schwappten erste Ideen, wie Hibernate in Richtung .NET über. Die Idee ist, ein Mapping von Code-Bestandteilen zu Datenbank-Inhalten durchzuführen. So gibt man also beispielsweise an, dass eine Klasse "Person" Daten aus einer Datenbanktabelle "Personen" laden soll. Der ORM kümmert sich dann um die notwendigen SQL-Befehle.Das ORM-Modell klappt ganz gut, hat aber einige Nachteile. Das ORM war z.B. nie fester Bestandteil des .NET-Frameworks. Man musste DLLs mitliefern und vor allem in der richtigen Version vorhalten. Wer schon einmal das bin-Verzeichnis einer ActiveRecord-Installation gesehen hat, weiß, dass der .NET-Einfachheitsgedanke hier irgendwie nicht gegriffen hat.
Microsoft hatte schließlich ein Einsehen und brachte Entity Framework als ersten echten Haus-ORM an den Start. Nach einigen Evolutions-Stufen ist nun ein Stand erreicht (Version 4), der ernst genommen werden muss. Die Idee ist hier, dass der Zugriffscode aus SQL-Sicht immer noch in einer DLL liegt, die allerdings Bestandteil des Frameworks selbst ist. Tools im Visual Studio erzeugen den Klassen-Code entweder komplett selbst, nachdem sie eine Datenbank analysiert haben oder man erstellt erst Entitäten und erzeugt daraus dann Datenbanken. codingfreaks neigt, wie in anderen Artikeln bereits beschrieben, dazu, von der zweiten Strategie abzuraten.
Daten verfügbar machen
Mit SOA hat das bisher geschrieben noch überhaupt nichts zu tun. Microsoft hat mit der Windows Communication Foundation (WCF) letztlich nur eine Bereinigung seiner teils konfusen Remoting- und Service-Technologien eingeführt, dies aber mit durchschlagendem Erfolg. WCF bringt standardkonforme Dienste in die .NET-Welt, die sauber programmiert und leicht konfiguriert werden können. Dadurch ergibt sich zwar noch lange nicht SOA, es wird aber endlich möglich, SOA-konforme Anwendungen mit Microsoft-Mitteln zu entwickeln.Ich werde hier jetzt keinen Abriss zu SOA verfassen. Zum einen sollte man dieses Thema eher in Buchform konsumieren (soabooks.com). Zum anderen glaube ich inzwischen, dass SOA zwar für viele Menschen unterschiedliche Bedeutungen hat, letztlich aber so ziemlich alle das gleiche Ziel haben. Abbildung 1 soll zeigen, was ich damit meine.
Irgendwo liegen also Daten vor, die wir per EF codeseitig zugreifbar machen. Dann erzeugen wir WCF-Komponenten, die diese Daten nutzen. Die WCF stellt dann Endpunkte nach “draußen” zur Verfügung. Fertig!
Man stellt sich also vor, dass eine Klasse “Employee” aus einer Tabelle “Employees” geladen per WCF über Systemgrenzen transportiert wird. Wir erhalten also von einem Webservice einen Employee, bearbeiten ihn und schreiben ihn zurück und fertig. Nur das es halt so einfach nicht ist!
Die Probleme
Zunächst einmal sind EF-Objekte nicht einfach nur Klassen, sondern eben Klassen mit einer gewissen Logik. Sie erben von Klassen des Entity Frameworks und können weitaus mehr, als nur die Daten einer Tabelle zur repräsentieren. Das führt aus Sicht von SOA zu dem Problem, dass keine Unabhängigkeit von der verwendeten Technologie mehr da ist. Würde in Abb. 1 rechts vom grauen Kasten eine Java-Anwendung auf unseren Employee zugreifen würde sie teilweise unsinnige Elemente erhalten und aus wäre es mit der schönen SOA-Welt.
Außerdem ist service-seitig das Objekt, das man clientseitig erhält nicht genau das gleiche, was service-seitig aus der Datenbank erzeugt wurde. Zwischen Service und Client werden vielmehr Proxy-Objekte ausgetauscht, weil wir ja vom Client aus nicht direkt in den Speicher des Services lesen und schreiben können (zum Glück). Also arbeitet WCF mit Serialisierung (Abb. 2).
“GetEmployee” ist hier eine Service-Methode, die scheinbar einfach einen Employee liefert. Es ist aber eine Serialisierung notwendig (gestrichelte Linie), um die Daten eines Employee zum Client zu transportieren. WCF macht es inzwischen so einfach, Dienste zu programmieren, dass wir oft vergessen, dass diese Serialisierung erfolgt. Aber sie ist da, nur dass WCF beim Einrichten eines Dienstverweises die entsprechende Klasse für uns erstellt.
Hat man einen Dienstverweis zu einem Projekt hinzugefügt und schaltet man in diesem Client-Projekt die Option zur Anzeige aller Dateien an, sieht man unterhalb des Dienstverweises die Datei Reference.cs (Abb. 3). Sie enthält die client-seitige Definition der Proxy-Klasse. Gegen diese Datei arbeiten wir im Client-Projekt. Nicht gegen das Original aus dem Entity Framework!
Die Vorstellung, dass WCF uns auf wundersame Weise Datenbank-Objekte des EF einfach durchschiebt, wir diese dann einfach auf einem anderen Rechner verändern und wieder speichern ist also Unsinn. Diese Feststellung ist ungemein wichtig, wie wir später noch sehen werden.
Die Idee der POCOs
Microsoft hat schnell erkannt, dass es ganz sinnvoll sein könnte, Entwicklern die Möglichkeit zu geben, anstelle der relativ schwergewichtigen EF-Objekte einfache POCOs (plain old CLR object) zu übertragen. Ein POCO zeichnet sich dadurch aus, dass es zum einen direkt von Object erbt und zum anderen keine magischen Datentypen nutzt, sondern alles mit Standard-Datentypen umsetzt. Ein POCO hat also die Macht, .NET-Klassen wirklich SOA-artig als Standard-Objekte verfügbar zu machen.
Damit das Ganze nun nicht in einem weiteren komplexen Mapping-Layer endet, der aus EF-Objekten POCOs generiert, hat man die T4-Idee (die letztlich auch hinter EF steckt) einfach angewendet und dem EF-Designer die Option, einzustellen, dass nicht die Standard-EF-Klassen, sondern andere Ausgaben erzeugt werden. Ich habe das Ganze mal in dem folgenden Webcast dargestellt:
[jwplayer file=”http://www.codingfreaks.de/files/videos/ef_poco.mp4“]
Es ist also wirklich nicht schwierig, einzustellen, was genau EF für uns erzeugen soll. Die Magie liegt hier bei T4.
POCOs sind nicht das richtige
Die POCOs sind in letzter Zeit in aller Munde, lösen aber unser SOA-Problem nicht. Sie sind deshalb so beliebt, weil immer mehr Anwendungen einfach nur Daten lesen wollen. Das hat mit dem wachsenden Interesse an Web-Anwendungen zu tun. Es ist mittlerweise state of the art, eine Webseite per AJAX und JSON mit Daten zu versorgen. Anstatt nun komplexe Datenzugriffe in PHP zu erzeugen, wird genau dies dem EF überantwortet und die Webseite greift bequem über REST-Schnittstellen auf die Ergebnisse zu. Dafür sind POCOs perfekt.
Sie bringen einen aber zum Verzweifeln, wenn man eine einfache Möglichkeit sucht, die ausgelieferten Entitäten wieder zu empfangen und so, wie sie kommen, in der Datenbank zu speichern. Genau hier helfen uns nun die sog. self-tracking-entities (STE) weiter. Sie sind erstmal nichts anderes, als eine weitere Codegenierungs-Strategie.
STE
STEs sind selbst auch POCOs. Sie erben von Object, implementieren aber zusätzliche 2 Interfaces: INotifyPropertyChanged und IObjectWithChangeTracker.
Was aber bringen uns die STE genau? Letztlich arbeiten auch sie mit Proxy-Klassen. Der Unterschied ist, dass die Entitäten neue Methoden und Möglichkeiten mitbringen. Vereinfacht gesagt erkennen STEs sich anhand ihrer Eigenschaft selbst wieder und ermöglichen somit den bidrektionalen Umgang mit der Datenbank über EF.
Bevor wir uns aber dem Einsatz zuwenden, soll der folgende Webcast zeigen, wie ein grundsätzliches und sehr ärgerliches Problem der STEs gelöst werden kann, das auftritt, wenn man die deutsche Version des Visual Studios einsetzt. Der folgende Webcast verdeutlicht das Problem und zeigt die Lösung auf:
[jwplayer config=“myplayer” file=”http://www.codingfreaks.de/files/videos/ef_ste_bug.mp4” image=“http://www.codingfreaks.de/files/videos/ef_ste_bug.png” html5_file=“http://www.codingfreaks.de/files/videos/ef_ste_bug.mp4” download_file=“http://www.codingfreaks.de/files/videos/ef_ste_bug.mp4”]
Dieser Fehler ist wirklich peinlich und hätte auf keinen Fall vorkommen dürfen, aber es ist mal wieder ein Beleg für die alte Weisheit, dass man als Entwickler eigentlich immer die Original-Sprache der Tools verwenden sollte.
Wie dem auch sei, wir haben nun die STEs erfolgreich eingebunden. Aber was bringt das nun genau? Listing 1 zeigt den Einsatz exemplarisch.
using (var ctx = new AdventureWorksEntities())
{
var adr = ctx.Addresses.FirstOrDefault();
Console.WriteLine(adr.AddressLine1);
adr.StartTracking();
adr.AddressLine1 += "2";
ctx.Addresses.ApplyChanges(adr);
ctx.SaveChanges();
Console.WriteLine(adr.ChangeTracker.ChangeTrackingEnabled);
adr.StopTracking();
}
Die Entitäten können per StartTracking
-Methode angewiesen werden, Änderungen an sich selbst automatisch zu sammeln. Die Schnittstelle INotifyPropertyChanged
hilft den STEs hier aus. ApplyChanges()
wiederum ist eine Methode, die auf Entitäts-Mengen angewendet werden kann. Sie nimmt ein Objekt vom gleichen Typ, wie die Entitätsmenge entgegen und wird nun anhand des Entitäts-Schlüssels nachsehen, ob es das der Methode übergebene Element bereits gibt. Wenn ja, wird ein Update durchgeführt. Wenn nein, dann ein Insert.
Das ist genau die Technik, die wir für unseren SOA-Einsatz brauchen.
Die nächsten Schritte zeigen nun, wie man STEs wirklich produktiv in eigenen Umgebungen aufsetzen kann. Die Komplexität der im Folgenden gezeigten Schritte rührt daher, dass wir nicht einfach nur STE dazu nehmen. Wir wollen vielmehr bereits eine praktikable Struktur aufbauen inkl. Diensten usw.
SOA mit EF und WCF
Für diejenigen, die am liebsten erst einen Webcast ansehen, hier die Inhalte in Videoform:
[jwplayer config=“myplayer” file=”http://www.codingfreaks.de/files/videos/ef_soa.mp4” image=“http://www.codingfreaks.de/files/videos/ef_soa.png” html5_file=“http://www.codingfreaks.de/files/videos/ef_soa.mp4” download_file=“http://www.codingfreaks.de/files/videos/ef_soa.mp4”]
Die folgende Auflistung zeigt die Projektschritte noch einmal in einer Schritt-für-Schritt-Anleitung auf:
- Leeres Solution-Projekt erstellen.
- Projekt vom Typ DLL hinzufügen (Model.csproj).
- Class1.cs entfernen.
- In Model.csproj ein ADO.NET-Entity-Model hinzufügen.
- Im Entity-Designer über “Neues Codegenierungselement hinzufügen…” STE erzeugen.
- Nacheinander die beiden *.tt-Dateien im Projektmappen-Explorer anklicken und in den Eigenschaften unter “Benutzerdefiniertes Tool” den Eintrag “TextTemplatingFileGenerator” entfernen.
- Alle Elemente unterhalb der beiden *.tt-Dateien entfernen.
- Neues Projekt vom Typ DLL der Solution hinzufügen (Entities.csproj)
- Class1.cs entfernen
- Verweis auf System.Runtime.Serialization hinzufügen.
- Dem Projekt über “Vorhandenes Element hinzufügen…” einen Link auf die Model.tt aus dem Projekt “Model.csproj” hinzufügen. Darauf achten, im Hinzufügen-Dialog “Als Linkhinzufügen…” zu benuzen.
- Neues Projekt vom Typ “WCF-Dienstanwendung” der Solution hinzufügen (Service.csproj)
- Dem Projekt Verweise auf System.Data.Entity, Model.csproj und Entities.csproj hinzufügen.
- In der Datei web.config den kompletten Schlüssel “connectionStrings” aus der App.config des Projektes Model.csproj einfügen.
- Analog zu Schritt 11 dem Projekt einen Link auf die Datei *.Context.tt aus dem Ordner von Model.csproj hinzufügen.
- Den neu erzeugten Link im Projektmappen-Explorer anklicken und im Eigenschaften-Fenster unter “Namespace des benutzerdefinierten Tools” den Namespace des Projektes Entities.csproj eintragen.
- Service-Methoden wie gewohnt erzeugen.
- Neues Projekt vom Typ “Konsolenanwendung” der Solution hinzufügen (ConsoleUI.csproj).
- Einen Verweis auf Entities.csproj hinzufügen.
- Einen Dienstverweis auf Service.csproj hinzufügen.
Jedes unterstrichene Element der Liste kennzeichnet das Hinzufügen eines neuen Projektes.
Das Entities-Projekt ist zum Zwecke der sauberen Kapselung vorhanden. Es erlaubt uns das Setzen eines Verweises im UI, ohne dass wir die Trennung der Schichten aufheben würden.
Wer anstelle der WCF-Dienstanwendung aus Schritt 12 eine Dienstbibliothek einsetzen möchte, wird auf Schwierigkeiten stoßen. Das hängt damit zusammen, dass eine WCF-Dienstanwendung Probleme hat, die Metadaten der in einem anderen Projekt liegenden EF-Designer-Dateien zu laden. Zur Lösung muss der ConnectionString hier manuell beim Erzeugen des Contexts übergeben werden.