In Code Reviews muss ich leider immer wieder feststellen das LINQ relativ selten eingesetzt wird. Als Begründung dafür höre ich des Öfteren, dass dieses Programmierkonzept entweder gar nicht gekannt wird oder das Unsicherheiten bei der Anwendung bestehen. Mich selbst ertappe ich zugegebenermassen auch manchmal dabei, wie ich zur Datenauswertung foreach-Loops implementiere statt auf LINQ zurückzugreifen.
Sollte es Ihnen auch so gehen, dass Sie LINQ gar nicht oder nur grundlegend kennen, dann nehmen Sie sich einfach zehn Minuten Zeit und lesen Sie diesen Artikel. Ich werde Ihnen in diesem Crashkurs einen Kurzüberblick über die grundlegenden LINQ Features geben. Nachdem Sie diesen Artikel gelesen haben, verfügen Sie über genügend Wissen um LINQ sofort in Ihren Projekten anwenden zu können.
Für den Crashkurs wird ein Grundverständnis der SQL Abfragen vorausgesetzt. Sie sollten vertraut sein mit Begriffen wie: select, from, where, group, join.
Einführung
LINQ ist eine Abkürzung für Language Integrated Query. Es handelt sich dabei um eine leistungsstarke Spracherweiterung zur Auswertung von Datenmengen. Die Datenmengen können aus den unterschiedlichsten Quellen wie beispielsweise aus Objekten, XML Dateien oder Datenbanken stammen. LINQ bietet eine deklarative Syntax welche Ähnlichkeiten mit SQL aufweist. Dadurch lassen sich Datenauswertungen sehr leicht erstellen und es entsteht ein gut verständlicher Quellcode.
Datenmenge für die Beispielanwendungen
Bevor ich Ihnen die erste LINQ Abfrage zeigen kann, benötigen wir eine Datenquelle. Für die Beispielabfragen in diesem Crashkurs werden Objekte als Datenquelle genutzt. Der nachfolgende Codeausschnitt zeigt die Daten, welche anschliessend in allen Beispielen verwendet werden.
1 private List<Customer> _customers;
2 private List<Order> _orders;
1 _customers = new List<Customer>()
2 {
3 new Customer() { Identifier = 1, FirstName = „Hans“, LastName = „Meier“ },
4 new Customer() { Identifier = 2, FirstName = „Uwe“, LastName = „Mueller“ },
5 new Customer() { Identifier = 3, FirstName = „Sandra“, LastName = „Schmidt“ }
6 };
7
8 _orders = new List<Order>()
9 {
10 new Order() { Identifier = 101, CustomerIdentifier = 2, TotalPrice = 750 },
11 new Order() { Identifier = 102, CustomerIdentifier = 2, TotalPrice = 80 },
12 new Order() { Identifier = 103, CustomerIdentifier = 2, TotalPrice = 120 },
13 new Order() { Identifier = 104, CustomerIdentifier = 3, TotalPrice = 55 }
14 };
Es werden zwei Listen erzeugt. Eine Kundenliste und eine Liste mit Bestellungen. In den Bestellungen wird jeweils die Kundennummer gespeichert. So lassen sich Verknüpfungen zwischen den beiden Listen erzeugen.
Einfache Datenabfragen
Nun möchte Ich sie nicht länger auf die Folter spannen und Ihnen die erste LINQ Abfrage zeigen. Es sollen alle Bestellungen abgefragt werden, welche Herr Müller getätigt hat. Dabei wird direkt die Identifikationsnummer des Kunden genutzt, also noch keine Verbindung zwischen den beiden Listen hergestellt.
1 var results = from order in _orders
2 where order.CustomerIdentifier == 2
3 select order.TotalPrice;
4
5 foreach (var result in results)
6 {
7 Console.WriteLine(result);
8 }
Die Anwendung erzeugt folgende Ausgabe:
750
80
120
Wie sie sehen ähnelt die Syntax sehr stark einer SQL Abfrage. Aus der _orders Liste werden alle Elemente selektiert welche die entsprechende Kundennummer aufweisen. Aus den so gefilterten Elementen wird jeweils nur der Wert TotalPrice selektiert. Als Ergebnis liefert die Abfrage somit eine neue Liste zurück, in der alle Preise der Bestellungen dieses Kunden enthalten sind.
Die deklarative Syntax in LINQ ist eigentlich nur syntaktischer Zuckerguss um den Softwareentwickler die Arbeit etwas zu versüssen. Im Hintergrund wird die Abfrage in funktionalen Code umgewandelt. Statt der deklarativen Syntax kann die Abfrage daher auch mittels Funktionen geschrieben werden. Der nachfolgende Codeausschnitt zeigt die gleiche Abfrage wie zuvor, aber diesmal in funktionaler Schreibweise.
1 var results = _orders.Where(order => order.CustomerIdentifier == 2)
2 .Select(order => order.TotalPrice);
Anhand dieses Quellcodes lässt sich erahnen wie LINQ programmiert ist. Mittels Erweiterungsmethoden und Lambda Expressions wurde LINQ als Fluent Interface implementiert. Dies soll aber als kleiner Blick hinter die Kulissen genügen. Für das Verständnis dieses Artikels ist die Kenntnis der angesprochenen Programmierkonzepte nicht erforderlich.
Wie Sie anhand der beiden Beispiele sehen können, dürfen Sie in LINQ sowohl deklarativ als auch funktional programmieren. Die deklarative Syntax ist aber klar vorzuziehen da sie einem besser lesbaren Quellcode resultiert. Eine abgeschlossene deklarative Anweisung kann aber mit funktionalem Code erweitert werden. Das nachfolgende Beispiel zeigt solch eine gemischte Schreibweise.
1 var results = (from order in _orders
2 where order.CustomerIdentifier == 2
3 select order.TotalPrice)
4 .ToArray();
Typ des Abfrageergebnisses
In den bisherigen Beispielen wurde nach dem select Schlüsselwort angegeben welcher Wert aus der Datenmenge in die Ergebnisliste übertragen werden soll. Dabei wurde nur der Wert TotalPrice selektiert. Es lassen sich selbstverständlich auch weitere Werte selektieren. Im Prinzip können die Werte in diversen Kombinationen zusammengestellt werden. Der nachfolgende Quellcode zeigt drei Beispiele. Im ersten Fall wird das komplette Kundenelement selektiert, im zweiten Beispiel wird nur ein Wert aus dem Kundenelement abgefragt und im dritten Fall wird ein völlig neues Element erzeugt welches die Werte Kosten und Steuern enthält.
1 var results1 = from order in _orders
2 where order.CustomerIdentifier == 2
3 select order;
4
5 var results2 = from order in _orders
6 where order.CustomerIdentifier == 2
7 select order.TotalPrice;
8
9 var results3 = from order in _orders
10 where order.CustomerIdentifier == 2
11 select new
12 {
13 Cost = order.TotalPrice,
14 Taxes = order.TotalPrice * 0.07
15 };
Die Abfrageergebnisse können also völlig frei definiert werden. Die LINQ Abfragen generieren dazu anonyme Typen.
Sortierung des Abfrageergebnisses
Genau wie bei SQL stellt sich auch bei LINQ die Frage wie die Ergebnisse sortiert sind. Wenn Sie keine Sortierung vorgeben dann sind die Ergebnisse in beiden Fällen zufällig sortiert. Wenn Sie die Ergebnisse in einer bestimmten Reihenfolge erwarten, dann müssen Sie dies in der LINQ Anweisung angeben. Dazu dient der orderby Befehl. Im nachfolgenden Beispiel werden die Daten zuerst mit aufsteigendem und danach mit absteigendem Preis sortiert.
1 var resultsAsc = from order in _orders
2 where order.CustomerIdentifier == 2
3 orderby order.TotalPrice
4 select order;
5
6 var resultsDesc = from order in _orders
7 where order.CustomerIdentifier == 2
8 orderby order.TotalPrice descending
9 select order;
Gruppierung
Analog zu SQL lassen sich auch in LINQ die Ergebnisse von Datenabfragen zu Gruppen zusammenfassen. Betrachten wir dazu die Liste mit Bestellungen. Ein typischer Anwendungsfall könnte lauten: Ermittle wie viele Bestellung jeder Kunde getätigt hat. Der nachfolgende Quellcode zeigt die entsprechende LINQ Anweisung bei welcher die Schlüsselwörter group/by/into eingesetzt werden um die Ergebnisse zu Gruppieren.
1 var results = from order in _orders
2 group order by order.CustomerIdentifier into g
3 select new { Identifier = g.Key, Count = g.Count() };
4
5 foreach (var result in results)
6 {
7 Console.WriteLine(result.Identifier + „: „ + result.Count);
8 }
Die Anwendung erzeugt folgende Ausgabe:
2: 3
3: 1
Verknüpfen von Datenmengen mittels Join
Die beiden Datenlisten wurden bisher ausschliesslich getrennt voneinander betrachtet. Die Liste mit Bestellungen enthält aber die Kundennummer. Damit lässt sich eine Verbindung zwischen beiden Datenmengen herstellen. Betrachten wir dazu folgendes Beispiel: Es sollen alle Preise der Bestellungen und der jeweils zugehörende Kunde aufgelistet werden. Der nachfolgende Quellcode zeigt eine mögliche Implementierung.
1 var results = from customer in _customers
2 join order in _orders on
3 customer.Identifier equals order.CustomerIdentifier
4 select new
5 {
6 Name = customer.LastName + „, „ + customer.FirstName,
7 order.TotalPrice
8 };
9
10 foreach (var result in results)
11 {
12 Console.WriteLine(result.Name + „: „ + result.TotalPrice);
13 }
In Zeile 2 wird mittels join/in/on/equals angegeben wie die beiden Listen verknüpft werden sollen. In diesem Fall durch erfolgt die Verknüpfung mittels der beiden Identifier Werte. Anschliessend werden die Ergebnisse in ein neues anonymes Objekt selektiert.
Die Anwendung erzeugt folgende Ausgabe:
Mueller, Uwe: 750
Mueller, Uwe: 80
Mueller, Uwe: 120
Schmidt, Sandra: 55
Aggregatfunktionen
LINQ unterstützt die typischen und aus SQL bekannten Aggregatfunktionen wie zum Beispiel: count, sum, min, max und average. Das nachfolgende Beispiel zeigt wie diese Funktionen verwendet werden können.
1 var count = (from order in _orders
2 where order.CustomerIdentifier == 2
3 select order.TotalPrice).Count();
4
5 var min = (from order in _orders
6 where order.CustomerIdentifier == 2
7 select order.TotalPrice).Min();
8
9 var max = (from order in _orders
10 where order.CustomerIdentifier == 2
11 select order.TotalPrice).Max();
12
13 var sum = (from order in _orders
14 where order.CustomerIdentifier == 2
15 select order.TotalPrice).Sum();
16
17 var average = (from order in _orders
18 where order.CustomerIdentifier == 2
19 select order.TotalPrice).Average();
20
21 Console.WriteLine(count);
22 Console.WriteLine(min);
23 Console.WriteLine(max);
24 Console.WriteLine(sum);
25 Console.WriteLine(average);
Die Anwendung erzeugt folgende Ausgabe:
3
80
750
950
316.6
Konvertierung zu List, Array oder Dictionary
Die Ergebnisse einer LINQ Anweisung lassen sich explizit in List, Array oder Dictionary Objekte umwandeln. Nachfolgend sehen Sie ein entsprechendes Beispiel, bei welchem die Befehle ToArray, ToList und ToDictionary verwendet wurden.
1 var array = (from order in _orders
2 where order.CustomerIdentifier == 2
3 select order).ToArray();
4
5 var list = (from order in _orders
6 where order.CustomerIdentifier == 2
7 select order).ToList();
8
9 var dictionary = (from order in _orders
10 where order.CustomerIdentifier == 2
11 select order).ToDictionary(order => order.Identifier);
Ein Dictionary benötigt immer einen Key Wert. In Zeile 11 wird in der ToDictionary Funktion der Identifier der Order als Key für das Dictionary angegeben.
Verzögerte Ausführung vs. sofortige Ausführung
Eine LINQ Anweisung gibt meistens ein Enumerable Objekt zurück. Die Anweisung wird dadurch erst ausgeführt wenn mittels foreach die Ergebnisse ausgelesen werden. Wird wie im vorhergehenden Beispiel ToArray, ToList oder ToDictionary aufgerufen, dann führt dies zur sofortigen Ausführung. Eine Anweisung kann somit verzögert oder sofort ausgeführt werden. Verzögert ausgeführte LINQ Abfragen sind dabei wiederverwendbar.
Das nachfolgende Beispiel zeigt eine Abfrage aller Preise der Bestellungen. Nachdem die Abfrage durchgeführt und die Ergebnisse ausgegeben wurden, wird eine weitere Bestellung in die Liste eingefügt. Die LINQ Abfrage wird danach erneut ausgeführt. Durch die verzögerte Auswertung mittels des Enumerable Objektes wird auch die neue Bestellung in die zweite Ausführung der Abfrage einbezogen.
1 var query = from order in _orders
2 where order.CustomerIdentifier == 2
3 select order.TotalPrice;
4
5 foreach (var result in query)
6 {
7 Console.WriteLine(result);
8 }
9
10 Console.WriteLine(„——„);
11
12 _orders.Add(new Order()
13 {
14 Identifier = 104,
15 CustomerIdentifier = 2,
16 TotalPrice = 555
17 });
18
19 foreach (var result in query)
20 {
21 Console.WriteLine(result);
22 }
Die Anwendung erzeugt folgende Ausgabe:
750
80
120
——
750
80
120
555
Wenn Sie die obige LINQ Abfrage um eine Konvertierung erweitern, zum Beispiel mit ToList, dann wird die Abfrage sofort ausgeführt. Nachfolgend sehen Sie die geänderte Abfrage.
1 var query = (from order in _orders
2 where order.CustomerIdentifier == 2
3 select order.TotalPrice).ToList();
Diese Anpassung führt dazu dass die Abfrage sofort eine Ergebnisliste generiert. In den beiden foreach-Schleifen wird somit jeweils nur die bereits erzeugte Ergebnisliste ausgewertet. In der zweiten Schleife wird daher die neu eingefügte Bestellung nicht einbezogen.
Die angepasste Anwendung erzeugt folgende Ausgabe:
750
80
120
——
750
80
120
Fazit
LINQ ist eine leistungsstarke Spracherweiterung zur Auswertung von Datenmengen. Komplexe Datenabfragen lassen sich auf einfache Weise erstellen und durchführen. LINQ erlaubt dabei die Verwendung einer deklarativen Syntax, welche zu sehr gut verständlichem Quellcode führt.
Pingback: Multithreading in C#, Teil 18: PLINQ (Parallel LINQ) | coders corner
Hallo ich bin C# Anfänger und möchte mich herzlich für den beitrag bedanken er hat wenigstens ansatzweise licht ins dunkel gebracht da ich erst seit ca 2 monaten programmiere