LINQ - Daten und Code endlich friedlich vereint

Die Abfragesprache LINQ ist einer der absoluten Höhepunkte des kommenden .NET Framework 3.5. Mit ihr lassen sich beliebige Datenquellen im Stil von SQL abfragen.

Artikel erschienen in Swiss IT Magazine 2007/20

     

Die Entwicklung einer Programmiersprache wird oft nur von einem sehr kleinen Personenkreis vorangetrieben. Manchmal ist es sogar nur eine Person, deren Vision für eine optimale Sprache für Softwareentwickler massgeblich für den Fortschritt der Sprache ist. Der kürzlich verstorbene John Backus gilt als der Erfinder von Fortran, das in den frühen 50er Jahren, neben Cobol, die erste richtige Programmiersprache war.



Die Professoren Kemeny und Kurtz stehen auch 40 Jahre später noch für ihre Sprache BASIC, mit der damals Studenten das Programmieren lernen sollten. Pascal (aus der später das legendäre Turbo Pascal hervorging) wurde Anfang der 70er Jahre von Prof. Niklaus Wirth an der ETH in Zürich er­dacht, und der Amerikaner Dennis Ritchie hat Ende der 60er Jahre zusammen mit seinen Kollegen Thompson und Kerninghan die Programmiersprache C geschaffen, die, zusammen mit ihrem direkten Nachfolger C++, heutzutage, allen modernen Trends zum Trotz, immer noch eine der am meisten eingesetzten Sprachen sein dürfte.
Allen diesen Sprachen ist gemeinsam, dass ihre geistigen Väter inzwischen der älteren Generation angehören, den Pionieren der klassischen Grossrechner-EDV.




Die heutigen Köpfe der Programmiersprachenszene dürften ihren Helden aus der Jugendzeit zwar noch gelegentlich huldigen, aber ansonsten bei fast allem, was eine moderne Programmiersprache ausmachen sollte, anderer Meinung sein. Die modernen «Helden» sind unter anderem Guido van Rossum, dessen Sprache Python inzwischen auch bei Microsoft hoch im Kurs steht, und natürlich Anders Hejlsberg, der in seiner noch jungen Karriere mit Turbo Pascal, Delphi und C# bereits drei «globale» Programmiersprachengenerationen schuf und offenbar immer noch voller Ideen und Tatendrang steckt.



Allerdings ist Microsoft kein Universitätslabor, in dem Ideen für neue Programmiersprachen auf einem Bierdeckel skizziert und voller Elan an einem Wochenende umgesetzt werden. Bei Unternehmen dieser Grössenordnung muss irgendwo das Wort «Business» vorkommen. Da auch die Entwicklung einer neuen Sprache Millionen verschlingt, von dem Risiko einmal abgesehen, in den Augen der kritischen Entwickleröffentlichkeit nicht zu bestehen, muss es sehr gute Gründe geben, wenn eine neue Programmiersprache ins Leben gerufen wird. Drei Programmiersprachen in Microsofts knapp 30jähriger Unternehmensgeschichte ist nicht gerade viel, so dass auch Anders Hejlsberg davon ausgehen dürfte, dass C# die letzte Sprache war, die er in seiner Karriere als Chefentwickler bei Microsoft mitgestalten konnte.



Doch neue Sprachen lösen nicht automatisch bestehende Probleme, zumal eine Sprache, die bereits von Millionen von Entwicklern in der ganzen Welt benutzt wird, nicht mehr durch eine neue Sprache, und sei sie noch so genial, ersetzt werden kann. Was sich einmal etabliert hat, hat grosse Chancen für die Ewigkeit, der Programmiersprachendinosaurier Cobol liefert für diese These einen eindrucksvollen Beweis.


Stop inventing new things

«Stop inventing new things» heisst daher das Motto, das Erik Meijer (ein weiterer genialer Theoretiker, der wie Anders Hejlsberg seit einigen Jahren in den Diensten von Microsoft steht) vor kurzem ausgegeben hat. Statt laufend neue Sprachelemente oder gar neue Sprachen zu erfinden, sollte man mit dem auskommen, was bereits vorhanden ist. Doch was soll man überhaupt erfinden, bieten C# und Visual Basic.NET nicht schon alles, was sich ein Entwickler wünschen könnte? Die Antwort auf diese Frage hängt wie so oft vom Betrachtungswinkel ab.

Wer seit Jahren an einer grossen Anwendung programmiert, wird im Detail sicher einiges vermissen, im grossen und ganzen mit seiner Sprache aber zufrieden sein. Wer jedoch öfter neue Anwendungen beginnt und dabei feststellt, dass in grundlegenden Bereichen das sprichwörtliche Rad immer wieder neu erfunden werden muss, wird die Sache etwas kritischer betrachten. Einer der grössten Nachteile praktisch aller modernen Programmiersprachen ist die Art und Weise, wie sie mit Informationen aus Datenbanken umgehen. Alleine aufgrund der Tatsache, dass es Dutzende, wenn nicht Hunderte von Datenbanksystemen geben dürfte, verlässt sich jede Sprache auf externe Schnittstellen, die wissen, wie sie an die Daten herankommen. Der Nachteil ist nicht nur, dass sich die Entwickler mehr oder weniger detailliert mit diesen Schnittstellen auskennen müssen, sie erhalten die Daten stets in einer Form, die nicht gerade optimal ist. Ein Befehl, der etwa eine aus einer Datenbank abgefragte Kundennummer in das Programm übernimmt, sieht typischerweise wie folgt aus:



KundenNr = tbKdn.Rows(0).Fields(«KundenNr»).Value




Nicht nur, dass der Befehl etwas unhandlich wirkt, das Programm erhält den Wert untypisiert, kann also nicht erkennen, ob es sich um eine ganze Zahl, eine Zeichenkette oder ganz etwas anderes handelt. Noch unhandlicher wird es, wenn Daten auf der Grundlage einer SQL-Abfrage in das Programm geholt werden sollen. Egal, ob SQL Server, Oracle Server oder MySQL, der Ablauf ähnelt sich seit Jahren wie ein Ei dem anderen: Der Entwickler öffnet die Verbindung, legt ein Befehlsobjekt an, versorgt es mit dem SQL-Statement – das oft noch aus «Einzelteilen» per (fehleranfälliger) String-Verknüpfung zusammengesetzt wird –, führt den Befehl aus, erhält einen oder mehrere Datensätze in Gestalt eines Recordset- oder DataTable-Objekts und muss diese Menge in einer Schleife durchlaufen, um an die (natürlich untypisierten) Daten heranzukommen, die dann in einer Objektliste oder einem schlichten Array abgelegt werden. Wäre es nicht sehr schön, wenn Entwickler einfach nur das folgende Statement schreiben könnten, um alle offenen Rechnungen als Rechnungsobjekte zu erhalten, die dann direkt im Programm weiterverarbeitet werden könnten?



OffeneRechnungen = Select KundenNr, Datum, Betrag From Rechnungsdaten Where R.Status = «Offen»



Dieses sogenannte Impendanzproblem (der eher aus der Hi-Fi-Technik bekannte Begriff umschreibt die bislang ein wenig problematische Verbindung zwischen Code und Daten) zu lösen, haben sich Anders Hejlsberg, Erik Meijer und andere bei Microsoft zum Ziel gesetzt. Das Ergebnis heisst Language Integrated Query Framework (LINQ) und ist eine der innovativsten Techniken, die in den letzten Jahren in Redmond entstanden sind.
Dank LINQ kann im kommenden C# 3.0 und VB 9.0 eine Abfrage einer Collection wie folgt im Programmcode formuliert werden:



Dim OffeneRechnungen = From R In Rechnungen Where R.Status = «Offen» Select R




Natürlich muss die Collection Rechnungen ihren (typisierten) Inhalt zuvor von irgendwo her erhalten haben. Entweder wurde sie auf klassische Art und Weise gefüllt oder sie hat per LINQ to SQL ihre Daten direkt aus einer SQL-Server-Datenbank erhalten. LINQ selber ist nur für die Abfrage zuständig. Woher die Daten kommen, spielt keine Rolle. Per LINQ kann daher alles abgefragt werden, was eine minimale Anforderung erfüllt und die Schnittstelle IEnumerable(Of T) implementiert.
LINQ ist kein «Embedded SQL». Schlüsselwörter wie From, Where und Select sind lediglich Spracherweiterungen, deren Namen man natürlich nicht ganz ohne Grund an die Datenbanksprache SQL angelehnt hat, die aber mit SQL nichts zu tun haben. Auch der Umstand, dass zuerst From und am Ende ein Select folgt, hat eine einfache Bedeutung. Er sorgt dafür, dass auf Where folgend für die Variable R ein komplettes Intelli­Sense angeboten werden kann und alle Members der Klasse Rechnung angeboten werden können. Und das, obwohl R nicht offiziell deklariert und daher auch nicht mit einer Typangabe versehen werden kann. Die Variablen sind, und das ist sehr wichtig, trotz aller Leichtigkeit streng typisiert.


LINQ in Action

LINQ bietet sehr viel mehr als nur einfache Select-Abfragen. Die folgende Abfrage ermittelt (bezogen auf die Northwind-Datenbank) die Bestellungen aller Kunden:



from o in Orders
join c in Customers on o.CustomerID equals
c.CustomerID
select c



Wie bei «richtigem» SQL gibt auch bei LINQ eine solche Abfrage deutlich mehr Spalten zurück als im allgemeinen erwünscht sind. Diese einzugrenzen ist auch bei LINQ kein Problem, es wird einfach ein neues Objekt «aus dem Nichts heraus» generiert, wobei dieses Objekt zwar namenlos, aber nicht typenlos ist:



from o in Orders
join c in Customers on o.CustomerID equals
c.CustomerID
select new { FirmenName = c.CompanyName, Ort = c.City }



Auch eine Gruppierung à la GroupBy in SQL ist möglich, wobei der gesamte Ausdruck dadurch aber bereits ein wenig komplizierter wird. Der folgende Ausdruck gibt die Anzahl an Bestellungen pro Ort zusammen mit dem Namen des Ortes aus:



from o in Orders
join c in Customers on o.CustomerID equals
c.CustomerID
group c by c.City into g
select new { Ort = g.Key, Anzahl = (from f in g select f).Count() }



Es ist wichtig zu verstehen, dass es sich hier nicht um eine mehr oder weniger willkürliche «Mixtur» von neu eingeführten Schlüsselwörtern handelt, sondern alles auf relativ einfachen Spracherweiterungen von C# 3.0 und Visual Basic 9.0 basiert. Extension Methods wie Count oder Distinct sind bereits vordefiniert, Entwicklern steht es frei, jederzeit weitere Extension Methods zu definieren, die ganz andere Operationen mit einer im Rahmen einer Abfrage temporär angelegten Auflistung durchführen. Und es ist auch wichtig zu verstehen, dass LINQ die SQL als Abfragesprache nicht ablösen soll, schliesslich wäre die obige Query mit SQL nicht unbedingt umständlicher:



Select City, Count(CompanyName) As Anzahl From Customers, Orders
Where Customers.CustomerID = Orders.CustomerID
Group By City
Order By Anzahl DESC



LINQ soll in erster Linie dort eingesetzt werden, wo man Daten ad hoc zusammenstellen muss. Dass es nicht immer die typischen Datenbankbeispiele sein müssen, macht das nächste Beispiel deutlich, welches Wörter aus einem String heraustrennt und nach ihrer Länge sortiert:



var worte = from wort in «In der Hitze der Nacht».Split()
orderby wort.Length
descending
select wort;



Der folgende Ausdruck findet Duplikate in der Auflistung Worte und gibt diese mit der Anzahl der Vorkommnisse aus:



var Duplikate =
from wort in worte

group wort.ToUpper() by wort.ToUpper() into g
where g.Count() > 1
select new { g.Key, Anzahl = g.Count() };



LINQ lernt man am besten an Beispielen. Microsoft gibt sich grosse Mühe, die Entwickler vorzubereiten und hat in der seit fast 2 Jahren andauernden Beta-Phase bereits unzählige Beispiele produziert (z.B. die 101 LINQ Samples für VB und C#; http://msdn2.microsoft.com/en-us/vcsharp/aa336746.aspx). Eines der besten «Lerntools» für LINQ ist das Freeware-Tool LINQPad (http://www.albahari.com/linqpad.html), mit dem sich LINQ-Ausdrücke (z.B. gegen eine SQL-Server-Datenbank) bequem direkt eingeben und testen lassen.


1,2,3 zum Datenlayer

LINQ ist zwar ein fester Bestandteil vom kommenden .NET 3.5 und damit von C# 3.0 und VB 9.0, hält sich bei Visual Studio 2008 aber ansonsten dezent im Hintergrund. Lediglich eine unscheinbare Vorlage für LINQ to SQL deutet auf seine Anwesenheit hin. Diese Vorlage stellt das Bindeglied zu einer SQL-Server-Datenbank her. Im ersten Schritt wird eine Vorlage zum Projekt hinzugefügt (sie wird als dbml-Datei abgelegt). Im zweiten Schritt werden die gewünschten Tabellen auf die Vorlage gezogen und gegebenenfalls einzelne Felder entfernt. Im dritten Schritt wird noch ein wenig programmiert, und fertig ist die Datenanbindung. Das Abrufen der Daten geschieht über das durch die Vorlage automatisch angelegte DataContext-Objekt:



Nw = New NwDataContext()
DataGridView1.DataSource = Nw.Employees




Auch das Aktualisieren der Daten ist lediglich ein Aufruf:



Nw.SubmitChanges()



So einfach wird der Datenbankzugriff in Zukunft in vielen Fällen sein.
Dass sich LINQ bei VS 2008 nur dezent im Hintergrund hält, ist ein weiterer Grund, der die neue Technik sympathisch macht. Sie soll weder vorhandene Datenbankschnittstellen von heute auf morgen ablösen, noch drängt sie Microsoft in den Mittelpunkt. Dies ist aber nicht etwa die neue Bescheidenheit in Redmond, sondern hat in erster Linie pragmatische Gründe. Das aktuelle LINQ 1.0 ist nur ein Vorläufer. Spannend wird es mit dem geplanten LINQ to Entities, das datenbankunabhängig ist und in die Domäne der O/R-Mapper eindringt. Noch spannender wird es beim «Computing in the Cloud»-Projekt von Microsoft, bei dem LINQ das Bindeglied zwischen gigantischen Datacentern spielen wird, die überall auf der Welt im Einsatz sein werden. Manchmal fangen Revolutionen ganz unauffällig an.




Artikel kommentieren
Kommentare werden vor der Freischaltung durch die Redaktion geprüft.

Anti-Spam-Frage: Wie hiess im Märchen die Schwester von Hänsel?
GOLD SPONSOREN
SPONSOREN & PARTNER