LINQ - Daten und Code endlich friedlich vereint
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 erdacht, 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.
«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
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.
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