LINQ (Abkürzung für Language Integrated Query; Aussprache Link) ist ein programmtechnisches Verfahren von Microsoft zum Zugriff auf Daten. LINQ wurde federführend von Erik Meijer entwickelt und erschien erstmals mit .NET Framework 3.5.

Ziel von LINQ Die Daten, auf die ein Programm zugreift, stammen aus unterschiedlichen Quellen. Dazu gehören interne Quellen wie Felder sowie Objekt-Listen und -Hierarchien; externe Quellen wie XML-Dokumente, Datenbanktabellen, Textdateien, Excel-Dateien, E-Mail-Nachrichten, SharePoint-Listen und viele andere.

Jede dieser Datenquellen hat ihre eigenen Zugriffsmethoden. Dies führt dazu, dass sich der Programmierer in jedem Einzelfall mit den Details der jeweiligen Methode beschäftigen muss. Ferner muss jedes Mal das Programm geändert werden, wenn Daten aus einer Quelle in eine Quelle eines anderen Typs verschoben werden.

LINQ versucht dieses Problem zu beseitigen, indem es innerhalb des .Net-Frameworks eine einheitliche Methode für jeglichen Datenzugriff zur Verfügung stellt. Die Syntax der Abfragen in LINQ ist ähnlich der von SQL. Im Unterschied zu SQL stellt LINQ jedoch auch Sprachelemente zur Verfügung, die zum Zugriff auf hierarchische und Netzwerk-Strukturen geeignet sind, indem sie die dort vorhandenen Beziehungen ausnutzen.

Arbeitsweise

mini|hochkant=2|Wichtige LINQ-Anbieter

LINQ ist eine Sammlung von Erweiterungsmethoden, die auf Monaden operieren. Zudem gibt es in einigen .NET-Sprachen wie C#, VB.NET und F# eigene Schlüsselwörter für eine vorbestimmte Menge an LINQ-Methoden. Monaden werden in .NET als generische Klassen oder Interfaces mit einzelnem Typargument (z. B. IEnumerable<T>, IObservable<T>) abgebildet.

LINQ-Anweisungen sind unmittelbar als Quelltext in .NET-Programme eingebettet. Somit kann der Code durch den Compiler auf Fehler geprüft werden. Andere Verfahren wie ADO und ODBC hingegen verwenden Abfragestrings. Diese können erst zur Laufzeit interpretiert werden; dann wirken Fehler gravierender und sind schwieriger zu analysieren.

Innerhalb des Quellprogramms in C# oder VB.NET präsentiert LINQ die Abfrage-Ergebnisse als streng typisierte Aufzählungen. Somit gewährleistet es Typsicherheit bereits zur Übersetzungszeit.

Sogenannte LINQ-Anbieter (englisch ) übersetzen die LINQ-Anweisungen in die speziellen Zugriffsmethoden der jeweiligen Datenquelle. Innerhalb der .NET-Plattform stehen unter anderem folgende Anbieter zur Verfügung:

LINQ to Objects zum Zugriff auf Objektlisten und -Hierarchien im Arbeitsspeicher LINQ to SQL zur Abfrage und Bearbeitung von Daten in MS-SQL-Datenbanken LINQ to Entities zur Abfrage und Bearbeitung von Daten im relationalen Modell von ADO.NET; LINQ to XML zum Zugriff auf XML-Inhalte LINQ to DataSet zum Zugriff auf ADO.NET-Datensammlungen und -Tabellen LINQ to SharePoint zum Zugriff auf SharePoint-Daten.

Wichtige Konzepte Die Beispiele sind, sofern nicht anders angegeben, in C#.

Verzögerte Auswertung LINQ-Ausdrücke werden nicht bei ihrer Definition ausgeführt, sondern wenn der Wert abgefragt wird. Dies wird als Lazy Evaluation (auch ) bezeichnet. Dadurch kann die Abfrage auch mehrfach verwendet werden.

var numbers = new List<int>() {1,2,3,4}; // list with 4 numbers

// query is defined but not evaluated var query = from x in numbers select x;

numbers.Add(5); // add a 5th number

// now the query gets evaluated Console.WriteLine(query.Count()); // 5

numbers.Add(6); // add a 6th number

Console.WriteLine(query.Count()); // 6

Die verzögerte Auswertung kann auf verschiedene Weisen implementiert werden. Beispielsweise verwendet LINQ to Objects Delegates, während LINQ to SQL stattdessen das IQueryable<T>-Interface implementiert. Um die Veränderung des Ergebnisses der Abfrage zu verhindern, muss diese in einen anderen Typ konvertiert (englisch: ) werden. Hierzu dienen Konvertierungsmethoden wie AsEnumerable(), ToArray(), ToList(), ToDictionary(), ToLookup() usw.

Falls die LINQ-Abfrage eine Funktion aufruft, die eine Ausnahmebehandlung erfordert, so muss der Try-Catch-Block die Verwendung der Abfrage umklammern und nicht deren Erstellung.

Auflösung von Erweiterungsmethoden LINQ-Funktionen werden als Erweiterungsmethoden (englisch: ) implementiert. Gegeben sei eine Aufzählung von Elementen eines Typs:

Über eine Erweiterungsmethode kann nun etwa die Where()-Methode zur Filterung nach beliebigen Kriterien (re-)implementiert werden:

public static class EnumerableExtensions { public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)

foreach (var element in source)

if (predicate(element)) yield return element;

Bestimmte .NET-Sprachen besitzen eigene Schlüsselwörter, um die Methoden aufzurufen:

var query = from e in employees where e.DepartmentId == 5 select e;

Diese Schlüsselwörter werden vom Compiler in die entsprechenden Methodenaufrufe aufgelöst:

var query = employees.Where(e => e.DepartmentId == 5).Select(e => e);

Da es sich um Erweiterungsmethoden handelt, muss der Compiler die Methodenaufrufe in einen Aufruf der Methoden der passenden Erweiterungsklasse auflösen:

var query = Enumerable.Select(EnumerableExtensions.Where(employees, e => e.DepartmentId == 5), e => e);

Wichtige Operatoren From From definiert die Datenquelle einer Abfrage () oder Unterabfrage (), sowie eine Bereichsvariable () die ein einzelnes Element der Datenquelle () repräsentiert. from rangeVariable in dataSource // ...

Abfragen können mehrere from-Operationen besitzen, um Joins von mehreren Datenquellen zu ermöglichen. Hierbei gilt zu beachten, dass die Join-Bedingung bei mehreren from-Operationen durch die Datenstruktur definiert wird und sich vom Konzept eines Joins in Relationalen Datenbanken unterscheidet.

var queryResults = from c in customers from o in orders select new { c.Name, o.OrderId, o.Price };

oder kürzer:

var queryResults = from c in customers, o in orders select new { c.Name, o.OrderId, o.Price };

Where Where definiert einen Filter auf den auszuwählenden Daten.

var queryResults = from c in customers from o in orders where o.Date > DateTime.Now - TimeSpan.FromDays(7) select new { c.Name, o.OrderId, o.Price };

Select Select definiert eine Projektion bzw. die Form des Ergebnisses der LINQ-Abfrage.

Häufig wird eine Teilmenge von Eigenschaften projiziert, wobei dafür anonyme Klassen verwendet werden (d. h. new { …, … }) bzw. ab C# 7.0 auch Wertetupel ((…, …)).

Group Group wird verwendet, um Elemente nach einem bestimmten Schlüssel zu gruppieren:

var groupedEmployees = from e in Employees group e by e.Department; // group by department

Als Schlüssel kann auch ein anonymer Typ verwendet werden, der sich aus mehreren Schlüsseln zusammensetzt:

var groupedEmployees = from e in Employees group e by new { e.Department , e.Age }; // group by department and age

Into Into kann verwendet werden um das Ergebnis einer select, group oder join-Operation in einer temporären Variable zu speichern.

var groupedEmployees = from e in Employees group e by e.Department into EmployeesByDepartment select new { Department = EmployeesByDepartment.Key, EmployeesByDepartment.Count() };

OrderBy und ThenBy OrderBy und ThenBy wird verwendet, um eine Liste von Elementen in aufsteigender Reihenfolge zu sortieren.

var groupedEmployees = from e in Employees orderby e.Age // order employees by age; youngest first thenby e.Name // order same-age employees by name; sort A-to-Z select e;

Mit Hilfe von OrderByDescending und ThenByDescending wird die Liste in absteigender Reihenfolge sortiert:

var groupedEmployees = from e in Employees orderby e.Age descending // oldest first thenby e.Name descending // sort Z-to-A select e;

Reverse Reverse kehrt die Reihenfolge der Elemente um.

Join Join ermöglicht Inner Joins, Group Joins und Left Outer Joins.

Inner Join Ein Inner Join bildet die äußere Datenquelle auf die innere Datenquelle ab und liefert ein „flaches“ Ergebnis zurück. Elemente der äußeren Datenquelle, zu denen kein passendes Element der inneren Datenquelle existiert, werden verworfen.

var productCategories = from c in categories // outer datasource join p in products // inner datasource on c.CategoryId equals p.CategoryId // categories without products are ignored select new { c.CategoryName, p.ProductName };

Group Join Ein Group Join erzeugt eine hierarchische Ergebnismenge. Hierbei werden die Elemente der inneren Datenquelle mit den entsprechenden Elementen der äußeren Datenquelle gruppiert. Elemente zu denen kein entsprechendes Element der äußeren Datenquelle existiert, werden mit einem leeren Array verbunden.

var productCategories = from c in categories join p in products on c.CategoryId equals p.CategoryId into productsInCategory select new { c.CategoryName, Products = productsInCategory };

Ein Group Join ist in SQL nicht abbildbar, da SQL keine hierarchische Ergebnismenge zulässt. VB.NET besitzt mit Group Join ein eigenes Schlüsselwort.

Left Outer Join Ein Left Outer Join bildet die äußere Datenquelle auf die innere Datenquelle ab und liefert ein „flaches“ Ergebnis zurück. Elemente der äußeren Datenquelle, zu denen kein passendes Element der inneren Datenquelle existiert, werden mit einem Standardwert versehen. Um den Standardwert zu definieren, wird die DefaultIfEmpty()-Erweiterungsmethode verwendet, die leere Aufzählungen in eine einelementige Aufzählung mit einem Standardwert wandelt:

var productCategories = from c in categories join p in products on c.CategoryId equals p.CategoryId into productsInCategory from pic in productsInCategory.DefaultIfEmpty( new Product(CategoryId = 0, ProductId = 0, ProductName = String.Empty)) select new { c.CategoryName, p.ProductName };

Let Let ermöglicht es das Ergebnis einer Teilabfrage in einer Variable zu speichern, um diese später in der Abfrage verwenden zu können.

var ordersByProducts = from c in categories join p in products on c.CategoryId equals p.CategoryId into productsByCategory let ProductCount = productsByCategory.Count() orderby ProductCount select new { c.CategoryName, ProductCount };

Any Any wird verwendet, um festzustellen, ob eine Sequenz leer ist oder ein bestimmtes Prädikat enthält. bool containsAnyElements = Enumerable.Empty<int>().Any(); // false bool containsSix = Enumerable.Range(1,10).Any(x => x == 6); // true

Contains Contains wird verwendet, um festzustellen, ob ein bestimmter Wert in einer Sequenz enthalten ist. bool containsSix = Enumerable.Range(1,10).Contains(6); // true

Skip und Take Skip wird verwendet, um eine bestimmte Anzahl von Elementen einer Sequenz zu überspringen. Take wird verwendet, um eine maximale Anzahl von Elementen einer Sequenz auszuwählen.

IEnumerable<int> Numbers = Enumerable.Range(1,10).Skip(2).Take(5); // {3,4,5,6,7}

Zusätzlich sind die Erweiterungsmethoden SkipWhile() und TakeWhile() definiert, für die in VB.NET eigene Schlüsselwörter definiert sind. Diese Methoden erlauben die Verwendung eines Prädikats, welches definiert welche Elemente übersprungen bzw. ausgewählt werden.

Distinct Distinct wird verwendet, um eindeutige Elemente einer Sequenz auszuwählen. IEnumerable<int> MultipleNumbers = new List<int>() {0,1,2,3,2,1,4}; IEnumerable<int> DistinctNumbers = MultipleNumbers.Distinct(); // {0,1,2,3,4}

Union, Intersect und Except Für eine Liste von Elementen können die Mengenoperatoren Union, Intersect und Except eingesetzt werden: var NumberSet1 = {1,5,6,9}; var NumberSet2 = {4,5,7,11};

var union = NumberSet1.Union(NumberSet2); // 1,5,6,9,4,7,11 var intersect = NumberSet1.Intersect(NumberSet2); // 5 var except = NumberSet1.Except (NumberSet2); // 1,6,9

Aggregate Aggregate wird verwendet, um eine Aggregat-Funktion auf eine Datenquelle anzuwenden.

var nums = new[]{1,2,3,4,5}; var sum = nums.Aggregate( (a,b) => a + b); // sum = 1+2+3+4+5 = 15