Von der Datenbank zur Oberfläche mit .NET Teil 1

Diese Serie konnte ich mangels Zeit noch nicht fortsetzen.
Deshalb möchte ich aber trotzdem diesen Teil veröffentlichen.
Wer Interesse am Source hat kann mir gerne schreiben.

Dies soll der erste Teil einer begleitenden Serie zu einer Artikel Serie auf Heise-Developer werden. In der Artikelserie „Von der Datenbank bis zur Oberfläche mit .NET“ wird der schichtweise Aufbau einer .NET-Anwendung dargestellt. Für mich zeigt diese Artikelserie ein Thema auf, dass für mich im Moment sehr relevant ist.

Der Original-Artikel verwendet den Model-First-Ansatz, um mit dem Entity Framework den Datenzugriff zu abstrahieren. Dadurch muss mit T4-Templates gearbeitet werden und es gibt, wie der Artikel erwähnt außerdem einen Fehler in der deutschen Version von Visual Studio, der einem das Leben schwer macht. Ein relativ neuer Ansatz ist Code-First, bei dem man nur Code schreibt. Daraus kann dann die Datenbank erstellt werden. Um keine Datenbankinstallation zu benötigen wird in diesem Beispiel Microsoft SQL Server Compact 4.0 verwendet. Die Datenbank wird vom Entity Framework erstellt. Außerdem wird das Beispiel in VB.Net erstellt, da sehr viele Beispiele im Internet in C# sind und wenige in VB.Net. Warum sollten die C#-Entwickler nicht auch mal einen Konverter benutzen. Dieser Artikel folgt der Struktur des Original-Artikels und beschreibt dabei die Unterschiede zwischen den Ansätzen.

WWWings_GO

Der Original-Artikel beginnt mit dem Klassenbibliotheksprojekt WWWings_GO. GO steht für Geschäftsobjekte. Die Abbildung im Original-Artikel zeigt den Aufbau sehr schön. Im Original wird nun das Datenmodell mit dem Designer von Visual Studio erzeugt. Für Code-First ist dies nicht notwendig. Hier werden einfache Objekte erstellt. Die Objekte müssen öffentliche Properties anbieten, welche später zu Datenbankfeldern werden. Verknüpfungen zwischen den Objekten werden ebenfalls durch einfache Properties erreicht. Für Beziehungen des Typs 1-zu-n und n-zu-m werden entsprechende Listen als Typ genutzt.
Für Werttypen, wie Date oder Integer, welche später in der Datenbank mit NULL belegt werden sollen, nutzt man den generischen Typ Nullable(Of T). Im Beispiel ist die Klasse Flug zu sehen.

Public Class Flug
    Public Property ID As Integer
    Public Property Abflugort As String
    Public Property Zielort As String
    Public Property Datum As Date
    Public Property Plaetze As Integer
    Public Property FreiePlaetze As Integer
    Public Property Passagiere As IList(Of Passagier)
    Public Property Pilot As Pilot

    Public ReadOnly Property Route() As String
        Get
            Return Me.Abflugort & " -> " & Me.Zielort
        End Get
    End Property
    Public Overrides Function ToString() As String
        Return "Flug #" & Me.ID & ": " & Me.Route & ": " & Me.FreiePlaetze & " von " & Me.Plaetze & " frei."
    End Function
End Class

Besonders interessant ist, dass diese Klassenbibliothek keine besonderen Abhängigkeiten hat. Weder das Entity Framework, noch SQL Server CE müssen von diesem Projekt aus referenziert werden. Die Objekte sind einfach und schnell zu verstehen. Der Original-Artikel erstellt sowohl Datenzugriff und Objekte in diesem Projekt und verschiebt sie danach mit einem Trick in die Datenzugriffsschicht. Mit Code-First ist kein Trick notwendig!

WWWings_DZS

Als nächstes wird die Klassenbibliothek WWWings_DZS erstellt. Diese bekommt Verweise auf das Entity Framework, System.Data.Entity und WWWings_GO. Die wichtigste Klasse für das Entity Framework ist WWWingsModellContext. Diese wurde im Vergleich zum Original etwas umbenannt. Statt …Container heißt die Klasse hier …Context. Die Klasse erbt von DbContext aus dem Namespace System.Data.Entity. Im Beispiel sind die wichtigsten Bestandteile der Klasse zu sehen und anschließend erklärt.

Public Class WWWingsModellContext
    Inherits DbContext

    Public Sub New()
        System.Data.Entity.Database.SetInitializer(Of WWWingsModellContext)(New DropCreateDatabaseIfModelChanges(Of WWWingsModellContext))
    End Sub

    Public Property Fluege As DbSet(Of Flug)
    Public Property Personen As DbSet(Of Person)
    Public Property Piloten As DbSet(Of Pilot)
    Public Property Passagiere As DbSet(Of Passagier)

    Protected Overrides Sub OnModelCreating(modelBuilder As System.Data.Entity.DbModelBuilder)
        MyBase.OnModelCreating(modelBuilder)
        modelBuilder.Conventions.Remove(Of Conventions.PluralizingTableNameConvention)()

        modelBuilder.Entity(Of Person).ToTable("Personen")
        modelBuilder.Entity(Of Flug).ToTable("Fluege")

        modelBuilder.Entity(Of Person)().Property(Function(p) p.ID).HasDatabaseGeneratedOption(ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)
        modelBuilder.Entity(Of Flug)().Property(Function(p) p.ID).HasDatabaseGeneratedOption(ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)
    End Sub
End Class

Die Properties vom Typ DbSet(Of T) stellen Zugriffspunkte auf die entsprechenden Klassen dar. Sie werden vom Entity Framework zur Laufzeit automatisch  mit passenden Instanzen versehen. Die Methode OnModelCreating ist dafür verantwortlich das Mapping zwischen Datenbank und Objekten zu erstellen. Viele Eigenschaften des Mappings werden über Konventionen automatisch festgelegt. Innerhalb dieser Methode können solche Konventionen deaktiviert werden und das Mapping kann beeinflusst werden. Hier wird mit durch den DbModelBuilder eine Fluent-API angeboten. Im Beispiel wird die Konvention Tabellennamen in den Plural zu setzen abgeschaltet, da die Namen deutsch sind und bis jetzt nur der englische Plural unterstützt wird. Anschließend werden den Klassen Flug und Person entsprechende Tabellennamen zugewiesen. Danach wird für die Properties mit dem Namen ID beider Klassen die Datenbankautomatik eingestellt. Personen bekommen automatisch einen Schlüssel, Flüge nicht.

Im Konstruktor der Klasse WWWingsModellContext wird der Datenbankinitialisierer gesetzt. Ab Version 4.3 wird eine Datenbankmigration unterstützt werden. In den aktuellen Versionen gehen die Daten der Datenbank verloren. Der gewählte Datenbankinitialisierer prüft mittels eines Hash-Wertes, ob sich das Modell geändert hat. Ist dies der Fall, wird die Datenbank gelöscht und eine neue, passend zum neuen Modell erstellt. Wenn keine Datenbank existert wird sie entsprechend angelegt. Für solch ein Beispielprojekt oder kleine Tools ist das eine schöne Option. Für große Software nicht. Hier sollte der Initialisierer auf Nothing gesetzt werden. Dies signalisiert dem Entity Framework, dass man selbst die Datenbank erstellt. Erstellt wird die Datenbank beim ersten Zugriff auf diese.

WWWings_GL

Die dritte Klassenbibliothek ist die Geschäftslogik, welche die folgenden Referenzen braucht: WWWings_GO, WWWings_DZS und EntityFramework.

…. to be continued! ….

 

Advertisements

Null-Objekt

In diesem Post will ich die Problematik von Nothing beleuchten und was man besser machen kann. Oft gibt es in Code viele Prüfungen auf Nothing. Andauernd könnte es sein, dass ein Objekt nicht gesetzt ist und es deshalb zu einer Exception kommt.   Oft dient die Prüfung nur der Vermeidung einer Exception. Seltener jedoch will man wirklich wissen, ob das Objekt nicht gesetzt ist. Ein bekanntes Muster ist das Null-Objekt. Dieses hilft das Problem zu umgehen.

Anstatt Nothing zu benutzen, wird ein Objekt benutzt, welches ein nicht gesetztes Objekt repräsentiert. Pro Vererbungshierarchie muss man ein Null-Objekt erzeugen. Es gibt aber auch Objekte, die einen Null-Zustand besitzen. Ein Beispiel hierfür ist eine leere Liste. Dies soll auch als erstes betrachtet werden. Betrachtet wird eine Funktion, die Tiere sucht und eine Auflistung (IEnumerable) von Tieren zurückgibt. Wenn kein Tier gefunden wurde, könnte die Funktion Nothing zurück geben. Dann würde jeder Entwickler der die Funktion nutzt das Ergebnis auf Nothing prüfen, um zu verhindern, dass es zu einer Exception kommt. Wird stattdessen aber eine leere Liste zurückgegeben, dann kann diese einfach in einer Schleife durchlaufen werden. Die Schleife wird dann einfach nicht ausgeführt. Der Entwickler kann den leeren Zustand aber auch abfragen, wenn er dies möchte. Im Gegensatz zur ersten Möglichkeit wird er aber nicht dazu gezwungen.

Die zweite Variante des Null-Objektmusters kommt bei der Vererbung/Polymorphie zum Einsatz. Eine Funktion die einen Logger (ILog) zurück gibt, könnte Nothing zurückliefern, weil kein Logger gefunden wurde. Dann müsste, bevor der Logger benutzt wird auf Nothing geprüft werden. Wenn stattdessen ein NullLogger zurückgeliefert wird, der das Interface ILog mit einer leeren Funktion implementiert, dann kann die Rückgabe sorgenfrei verwendet werden. Will der Entwickler wirklich wissen, ob er einen Logger bekommen hat, kann dies Beispielsweise mit instanceof geprüft werden. Oder es kann eine Funktion angeboten werden, die eine solche Prüfung übernimmt.

Ein Nachteil des Musters ist, dass Fehler übersehen werden könnten. Denn wenn keine Exception kommt, könnte der Entwickler denken das alles in Ordnung ist. Um dieses Problem zu mindern, sollte in die Funktion, die leer gelassen wird, wenigstens ein Debug.WriteLine eingefügt werden. Dadurch kann der Entwickler auf den Umstand des Null-Objekts hingewiesen werden.

Im Folgenden ist das Beispiel mit dem ILog-Interface noch einmal aufgeführt:

Public Interface ILog
    Sub Log(message As String)
End Interface

Module LoggerFactory

    Private loggerList As IDictionary(Of String, ILog)

    .....

    Public Function GetLogger(id As String) As ILog
        If loggerList.ContainsKey(id) Then
            Return loggerList.Item(id)
        Else
            Return New NullLogger()
        End If
    End Function
End Module

Public Class NullLogger
    Implements ILog

    Public Sub Log(message As String) Implements ILog.Log
        Debug.WriteLine("Dies ist ein Null-Objekt. Es wurde kein Logger gefunden.")
    End Sub
End Class

Module MyProgramm
    Sub Main()
        Dim logger As ILog = LoggerFactory.GetLogger("MyLogger")
        logger.Log("Ein Eintrag")
        logger.Log("Noch ein Eintrag")
    End Sub
End Module

Zusammenfassend kann man sagen, dass das Null-Objekt Muster helfen kann überflüssige Prüfungen auf Nothing zu vermeiden. Außerdem kommt es zu weniger Exceptions, die aufgrund von vergessenen Prüfungen entstehen können. Die Gefahr Fehler zu übersehen kann mit Hilfe von Debug.WriteLine oder einem Logger gemildert werden.

Warum man AndAlso/OrElse in VB.Net verwenden sollte…

VB.Net hat neben den Operatoren And und Or auch die Operatoren AndAlso und OrElse. Diese werden auch als Kurzschluss-Operatoren bezeichnet. Der Unterschied zwischen den Operatoren ist, dass AndAlso und OrElse die Überprüfung abbrechen, sobald das Ergebnis klar ist.

Ich wusste lange nicht, dass es für diesen Zweck extra Operatoren gibt und dachte, dass dieses Verhalten der Standard ist. Und meiner Meinung nach, sollte es der Standard sein. Denn wenn der erste Teil einer And-Bedingung False ist, dann ist das Ergebnis des zweiten Teils irrelevant für das Gesamtergebnis der Bedingung. Warum sollte man also besser immer AndAlso und OrElse verwenden?

Punkt 1: Performance.
Da bereits nach der ersten Bedingung einer If-Bedingung das Ergebnis bekannt sein kann, müssen die anderen Prüfungen nicht mehr durchgeführt werden. Gerade wenn in der Überprüfung beispielsweise Datenbankzugriffe ausgeführt werden, kann viel Performance gespart werden. Aber auch bei kleinen Prüfungen kann Performance gespart werden. Nämlich dann, wenn viele kleinen Überprüfungen nicht mehr ausgeführt werden.

Punkt 2: Subtile Bugs (Unoffensichtliches Verhalten)
Das der zweite Teil eine And-Bedingung auch ausgeführt wird, wenn der erste Teil False war, ist nicht offensichtlich und verleitet zu Fehlern. Deshalb sollte immer AndAlso und OrElse verwendet werden. Ein Beispiel:

Private Function Test(obj As Object) As Boolean
    If obj Is Nothing Then
        Throw New NullReferenceException("Objekt nicht gesetzt.")
    End If
    
    Return True
End Function

Public Sub Main()
    Dim obj As Object = Nothing
    If obj IsNot Nothing And Test(obj) Then
        Console.WriteLine("Okay.")
    End If
End Sub

Die Methode Test wirft eine Exception, wenn die Übergabe Nothing ist. Dies simuliert eine Funktion, welche die Übergabe von Nothing nicht verträgt. Um Fehler direkt abzufangen wird die Variable auf Nothing geprüft und die Methode Test mit der Variablen aufgerufen. Die Ausführung von Main wirft die NullReferenceException, weil der zweite Block ausgeführt wird. Nicht sehr intuitiv. Schreibt man die Main-Methode aber so:

Public Sub Main()
    Dim obj As Object = Nothing
    If obj IsNot Nothing AndAlso Test(obj) Then
        Console.WriteLine("Okay.")
    End If
End Sub

Dann funktioniert es, wie erwartet, indem die Funktion Test nicht ausgeführt wird.

Meine Empfehlung ist also AndAlso und OrElse immer zu verwenden, um solche Bugs zu umgehen und um, wenigstens ein wenig, mehr Performance aus den eigenen Programmen zu holen.

Eine weitere Falle in Javascript!

Da will man in Ruhe eine kleine for-Schleife programmieren, und die will einfach nicht laufen…

var i;
for(i=0; i < data.lenght; i++){
    cooleAnweisungen(data[i]);
}

Nur wurde diese Schleife nie betreten, egal, wieviele Elemente das data-Array hat.

Woran lag es!? Ich habe length falsch geschrieben. Schwer zu sehen, durch Betriebsblindheit. Aber trotzdem finde ich, dass JavaScript ein komisches Verhalten zeigt.

Wäre data undefined, dann würde ein Fehler ausgelöst, da aber lenght undefined ist, passiert nichts. Schlimmer noch! Es gibt keinen Fehler und undefined ist nicht größer als i, was im ersten moment 0 ist. Dadurch wird die Schleife nie betreten, es wird aber auch kein Fehler gemeldet.

Also, immer gut auf das geschriebene in JavaScript achten!

CSS Generierung mit Stylus

Um eine große, komprimierte CSS-Datei zu bekommen und diese trotzdem noch wartbar zu halten, begab ich mich auf die Suche nach einem Tool für Node und Express. Dabei stieß ich auf Stylus.

Es ist einfaches CSS, nur ohne Klammern und Semikolons. Und man kann Javascript benutzen. Es wird auf der Serverseite beim Start des Servers in eine CSS-Datei compiliert und kann dabei auch direkt komprimiert werden.

Die @import Anweisung aus CSS wird verwendet, um dem Browser mitzuteilen, dass er an dieser Stelle eine weitere CSS-Datei nachladen soll. Stylus kennt diese Anweisung und wenn die Angegebene Datei auf „.css“ endet, dann wird die Anweisung auch als solche im Fertigen CSS stehen. Ist allerdings eine „.styl“-Datei angegeben, dann wird diese vor dem Compilieren eingefügt. Hierdurch kann eine Masterdatei mit Imports erstellt werden und das CSS in einzelne Dateien aufgeteilt werden.

Stylus compiliert aus solch einer Zusammenstellung eine große CSS-Datei. So ist das CSS (Stylus) wartbar, durch die einzelnen Dateien und es entsteht nur eine Datei, welche der Browser einmal lädt und dann cachen kann.

Ein kleiner Bonus:
Durch die Möglichkeit Variablen zu benutzen, durch einfache Anweisungen wie MainColor = #123456 kann nun eine „.styl“-Datei erstellt werden, welche alle Farben mit Namen definiert. Zum Beispiel Color1, Color2, … und Complement1, Complement2, … Diese wird mit der Import-Anweisung eingefügt und stellt so den anderen Styl-Dateien die Variablen zur Verfügung. Wenn sich nun die Farbpalette der Seite ändert, muss nur die Datei Colors.styl verändert werden.

Standardwerte in Jade-Templates

Folgendes Problem:
Ich habe ein Jade-Layout und ein Jade-Template. Ich möchte den Titel-Tag beim Rendern der Seite bestimmen. Das Layout-Template sah so aus (Funktioniert nicht!):

!!! strict
html
  head
    title #{title}
  body
    #kopfzeile Guten Tag!
    #inhalt!= body

Dies funktionierte auch, wenn man es wie folgt aus Express aufrief:

res.render('template.jade', {title: "Mein Titel"});

Nun wollte ich an manchen Stellen das Attribut einfach nicht verwenden und bekam eine Fehlermeldung.
title is not defined
Ich hab mich dann im Git-Repository umgesehen und diesen Eintrag gefunden.
Die Lösung ist, statt direkt auf die Eigenschaft zuzugreifen das locals-Objekt zu benutzen:

!!! strict
html
  head
    title= (locals.title || "Standardtitel")
  body
    #kopfzeile Guten Tag!
    #inhalt!= body

Dies kann nun so aufgerufen werden:

res.render('template.jade');

Oder mit gesetztem Titel:

res.render('template.jade', {locals:{title:"Mein Titel"}});

Nur ein kurzer Post zu Node.js

Mein aktuelles Projekt benutzt nun Node.js mit MongoDB. Mongoose bietet mir dabei den Datenzugriff auf die MongoDB, was ganz gut funktioniert. Außerdem habe ich mich nun doch entschlossen neben Express.js auch die Template Engine Jade einzusetzen. Ich finde die Idee, solch eine hübsche Syntax zu haben, durch die dann HTML erstellt wird sehr gut! Bis auf eine Kleinigkeit funktioniert es auch sehr gut. Wenn man eine Variable im Template anzeigt, diese aber nicht setzt, dann wird ein Fehler ausgelöst und die Seite wird nicht angezeigt. Hier wäre es schöner, wenn man stattdessen nichts anzeigen könnte, da manche Information nicht mitkommt, weil sie nicht benötigt wird. Naja, ich werde sehen, ob sich dafür eine hübsche Lösung findet.