Wprowadzenie do zapytań LINQ w języku C#

Zapytanie to wyrażenie , które pobiera dane ze źródła danych. Różne źródła danych mają różne natywne języki zapytań, na przykład SQL dla relacyjnych baz danych i XQuery dla języka XML. Deweloperzy muszą poznać nowy język zapytań dla każdego typu źródła danych lub formatu danych, który musi obsługiwać. LINQ upraszcza tę sytuację, oferując spójny model języka C# dla rodzajów źródeł danych i formatów. W zapytaniu LINQ zawsze pracujesz z obiektami języka C#. Te same podstawowe wzorce kodowania służą do wykonywania zapytań i przekształcania danych w dokumentach XML, bazach danych SQL, kolekcjach platformy .NET i innych formatach, gdy dostawca LINQ jest dostępny.

Trzy części operacji zapytania

Wszystkie operacje zapytań LINQ składają się z trzech odrębnych akcji:

  1. Uzyskaj źródło danych.
  2. Utwórz zapytanie.
  3. Wykonaj zapytanie.

W poniższym przykładzie pokazano, jak trzy części operacji zapytania są wyrażane w kodzie źródłowym. W przykładzie użyto tablicy całkowitej jako źródła danych dla wygody; jednak te same pojęcia dotyczą również innych źródeł danych. Ten przykład jest określany w pozostałej części tego artykułu.

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

Poniższa ilustracja przedstawia pełną operację zapytania. W LINQ wykonywanie zapytania różni się od samego zapytania. Innymi słowy, nie pobierasz żadnych danych, tworząc zmienną kwerendy.

Diagram pełnej operacji zapytania LINQ.

Źródło danych

Źródło danych w poprzednim przykładzie jest tablicą, która obsługuje interfejs ogólny IEnumerable<T> . Ten fakt oznacza, że może być odpytywane za pomocą LINQ. Zapytanie jest wykonywane w instrukcji foreach i foreach wymaga IEnumerable lub IEnumerable<T>. Typy obsługujące lub pochodne interfejsy, IEnumerable<T> takie jak typy ogólne IQueryable<T> , są nazywane typami z możliwością wykonywania zapytań.

Typ z możliwością wykonywania zapytań nie wymaga modyfikacji ani specjalnego traktowania, aby służyć jako źródło danych LINQ. Jeśli dane źródłowe nie są jeszcze w pamięci jako typ możliwy do wykonania zapytania, dostawca LINQ musi reprezentować je jako taki. Na przykład LINQ to XML ładuje dokument XML do typu z możliwością XElement wykonywania zapytań:

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Za pomocą elementu EntityFramework utworzysz mapowanie relacyjne obiektów między klasami języka C# i schematem bazy danych. Zapytania są zapisywane względem obiektów, a w czasie wykonywania element EntityFramework obsługuje komunikację z bazą danych. W poniższym przykładzie Customers reprezentuje określoną tabelę w bazie danych, a typ wyniku IQueryable<T>zapytania , pochodzi z .IEnumerable<T>

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

Aby uzyskać więcej informacji na temat tworzenia określonych typów źródeł danych, zobacz dokumentację różnych dostawców LINQ. Jednak podstawowa reguła jest prosta: źródło danych LINQ to dowolny obiekt obsługujący interfejs ogólny IEnumerable<T> lub interfejs, który dziedziczy z niego, zazwyczaj IQueryable<T>.

Uwaga

Typy, takie jak ArrayList obsługują interfejs niegeneryczny IEnumerable , mogą być również używane jako źródło danych LINQ. Aby uzyskać więcej informacji, zobacz How to query an ArrayList with LINQ (C#)(Jak wykonywać zapytania dotyczące tablicylist za pomocą LINQ (C#).

Zapytanie

Zapytanie określa, jakie informacje mają być pobierane ze źródła danych lub źródeł. Opcjonalnie zapytanie określa również sposób sortowania, grupowania i kształtowania tych informacji przed zwróceniem. Zapytanie jest przechowywane w zmiennej zapytania i inicjowane za pomocą wyrażenia zapytania. Składnia zapytań języka C# służy do pisania zapytań.

Zapytanie w poprzednim przykładzie zwraca wszystkie liczby parzystki z tablicy całkowitej. Wyrażenie zapytania zawiera trzy klauzule: from, wherei select. (Jeśli znasz język SQL, zauważysz, że kolejność klauzul jest odwracana od kolejności w języku SQL). Klauzula from określa źródło danych, where klauzula stosuje filtr, a klauzula select określa typ zwracanych elementów. Wszystkie klauzule zapytania zostały szczegółowo omówione w tej sekcji. Na razie ważnym punktem jest to, że w LINQ zmienna kwerendy nie podejmuje żadnej akcji i nie zwraca żadnych danych. Po prostu przechowuje informacje wymagane do wygenerowania wyników po wykonaniu zapytania w pewnym późniejszym momencie. Aby uzyskać więcej informacji o sposobie konstruowania zapytań, zobacz Standardowe operatory zapytań — omówienie (C#).

Uwaga

Zapytania mogą być również wyrażane przy użyciu składni metody. Aby uzyskać więcej informacji, zobacz Składnia zapytań i składnia metody w LINQ.

Klasyfikacja standardowych operatorów zapytań według sposobu wykonywania

Implementacje LINQ to Objects standardowych metod operatorów zapytań są wykonywane na jeden z dwóch głównych sposobów: natychmiastowe lub odroczone. Operatory zapytań, które korzystają z odroczonego wykonywania, mogą być dodatkowo podzielone na dwie kategorie: przesyłanie strumieniowe i niestreamingowe.

Natychmiastowe

Natychmiastowe wykonanie oznacza, że źródło danych jest odczytywane, a operacja jest wykonywana raz. Wszystkie standardowe operatory zapytań zwracające wynik skalarny są wykonywane natychmiast. Przykłady takich zapytań to Count, , AverageMaxi First. Te metody są wykonywane bez jawnej foreach instrukcji, ponieważ samo zapytanie musi być używane foreach w celu zwrócenia wyniku. Te zapytania zwracają pojedynczą wartość, a nie IEnumerable kolekcję. Możesz wymusić natychmiastowe wykonanie dowolnego zapytania przy użyciu Enumerable.ToList metod lub Enumerable.ToArray . Natychmiastowe wykonywanie zapewnia ponowne użycie wyników zapytania, a nie deklaracji zapytania. Wyniki są pobierane raz, a następnie przechowywane do użycia w przyszłości. Następujące zapytanie zwraca liczbę parzystki w tablicy źródłowej:

var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

Aby wymusić natychmiastowe wykonanie dowolnego zapytania i buforować jego wyniki, możesz wywołać ToList metody lub ToArray .

List<int> numQuery2 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToArray();

Można również wymusić wykonywanie, umieszczając pętlę foreach bezpośrednio po wyrażeniu zapytania. Jednak przez wywołanie ToList metody lub ToArray buforowanie wszystkich danych w jednym obiekcie kolekcji.

Odłożone

Odroczone wykonanie oznacza, że operacja nie jest wykonywana w punkcie kodu, w którym jest zadeklarowane zapytanie. Operacja jest wykonywana tylko wtedy, gdy zmienna kwerendy jest wyliczana, na przykład przy użyciu foreach instrukcji . Wyniki wykonywania zapytania zależą od zawartości źródła danych, gdy zapytanie jest wykonywane, a nie po zdefiniowaniu zapytania. Jeśli zmienna kwerendy jest wyliczana wiele razy, wyniki mogą się różnić za każdym razem. Prawie wszystkie standardowe operatory zapytań, których typ zwracany jest IEnumerable<T> lub IOrderedEnumerable<TElement> są wykonywane w sposób odroczony. Odroczone wykonanie zapewnia funkcję ponownego użycia zapytań, ponieważ zapytanie pobiera zaktualizowane dane ze źródła danych za każdym razem, gdy wyniki zapytania są iterowane. Poniższy kod przedstawia przykład odroczonego wykonywania:

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

Instrukcja foreach jest również miejscem pobierania wyników zapytania. Na przykład w poprzednim zapytaniu zmienna num iteracji przechowuje każdą wartość (pojedynczo) w zwróconej sekwencji.

Ponieważ sama zmienna kwerendy nigdy nie przechowuje wyników zapytania, możesz wykonać ją wielokrotnie, aby pobrać zaktualizowane dane. Na przykład oddzielna aplikacja może stale aktualizować bazę danych. W aplikacji można utworzyć jedno zapytanie, które pobiera najnowsze dane, i można je wykonać w odstępach czasu w celu pobrania zaktualizowanych wyników.

Operatory zapytań używające wykonywania odroczonego mogą być dodatkowo klasyfikowane jako przesyłanie strumieniowe lub niesłane.

Przesyłanie strumieniowe

Operatory przesyłania strumieniowego nie muszą odczytywać wszystkich danych źródłowych przed uzyskaniem elementów. W momencie wykonywania operator przesyłania strumieniowego wykonuje operację na każdym elemecie źródłowym, ponieważ jest odczytywany i zwraca element, jeśli jest to odpowiednie. Operator przesyłania strumieniowego nadal odczytuje elementy źródłowe do momentu utworzenia elementu wyniku. Oznacza to, że w celu wygenerowania jednego elementu wyniku może zostać odczytany więcej niż jeden element źródłowy.

Brak transmisji strumieniowej

Operatory nieuwzwiązane z transmisją strumieniową muszą odczytywać wszystkie dane źródłowe, zanim będą mogły uzyskać element wynikowy. Operacje, takie jak sortowanie lub grupowanie, należą do tej kategorii. W czasie wykonywania operatory zapytań nieuwzwiązanych z transmisją strumieniową odczytują wszystkie dane źródłowe, umieszczają je w strukturze danych, wykonują operację i dają wynikowe elementy.

Tabela klasyfikacji

Poniższa tabela klasyfikuje każdą standardową metodę operatora zapytania zgodnie z metodą wykonywania.

Uwaga

Jeśli operator jest oznaczony w dwóch kolumnach, dwie sekwencje wejściowe są zaangażowane w operację, a każda sekwencja jest oceniana inaczej. W takich przypadkach jest to zawsze pierwsza sekwencja na liście parametrów, która jest obliczana w sposób odroczony, przesyłany strumieniowo.

Standardowy operator zapytania Typ zwracany Natychmiastowe wykonanie Odroczone wykonywanie przesyłania strumieniowego Odroczone wykonywanie niesłaniające transmisji strumieniowej
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Pojedyncza wartość liczbowa X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max Pojedyncza wartość liczbowa, TSourcelub TResult? X
Min Pojedyncza wartość liczbowa, TSourcelub TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Pojedyncza wartość liczbowa X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray TSource[] Tablicy X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

LINQ to objects

"LINQ to Objects" odnosi się do użycia zapytań LINQ bezpośrednio z dowolną IEnumerable kolekcją lub IEnumerable<T> kolekcją. Za pomocą LINQ można wykonywać zapytania dotyczące dowolnych kolekcji możliwych do wyliczenia, takich jak List<T>, Arraylub Dictionary<TKey,TValue>. Kolekcja może być zdefiniowana przez użytkownika lub typ zwracany przez interfejs API platformy .NET. W podejściu LINQ piszesz kod deklaratywny, który opisuje, co chcesz pobrać. LINQ to Objects oferuje doskonałe wprowadzenie do programowania za pomocą LINQ.

Zapytania LINQ oferują trzy główne zalety w przypadku tradycyjnych foreach pętli:

  • Są one bardziej zwięzłe i czytelne, zwłaszcza podczas filtrowania wielu warunków.
  • Zapewniają zaawansowane funkcje filtrowania, porządkowania i grupowania z minimalnym kodem aplikacji.
  • Można je przenosić do innych źródeł danych bez żadnych modyfikacji.

Tym bardziej złożona operacja, którą chcesz wykonać na danych, tym większa korzyść przy użyciu linQ zamiast tradycyjnych technik iteracji.

Przechowywanie wyników zapytania w pamięci

Zapytanie to w zasadzie zestaw instrukcji dotyczących pobierania i organizowania danych. Zapytania są wykonywane z opóźnieniem, ponieważ każdy kolejny element w wyniku jest żądany. Gdy używasz foreach metody do iterowania wyników, elementy są zwracane jako dostępne. Aby ocenić zapytanie i zapisać wyniki bez wykonywania foreach pętli, wystarczy wywołać jedną z następujących metod w zmiennej zapytania:

Zwrócony obiekt kolekcji należy przypisać do nowej zmiennej podczas przechowywania wyników zapytania, jak pokazano w poniższym przykładzie:

List<int> numbers = [1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];

IEnumerable<int> queryFactorsOfFour =
    from num in numbers
    where num % 4 == 0
    select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

Zobacz też