Crashkurs: LINQ in 10 Minuten

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.

Werbung
Dieser Beitrag wurde unter .NET, C#, Crashkurs, LINQ abgelegt und mit , , , , , , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

2 Antworten zu Crashkurs: LINQ in 10 Minuten

  1. Pingback: Multithreading in C#, Teil 18: PLINQ (Parallel LINQ) | coders corner

  2. Anonymous schreibt:

    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

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s