Udostępnij za pośrednictwem


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żna go zapytać przy użyciu LINQ. Zapytanie jest wykonywane w instrukcji foreach i foreach wymaga IEnumerable lub IEnumerable<T>. Typy obsługujące IEnumerable<T> lub pochodny interfejs, taki jak ogólny 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. Pisz swoje zapytania względem obiektów, a w czasie wykonywania 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 parzyste z tablicy liczb całkowitych. 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.

Bezpośredni

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. Metody te są wykonywane bez jawnej foreach instrukcji, ponieważ samo zapytanie musi używać 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 ilość parzystych liczb 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, wywołując ToList lub ToArray, także buforujesz wszystkie dane 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 iteracyjna num przechowuje każdą wartość (jedną na raz) 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ń, które używają wykonywania odroczonego, mogą być dodatkowo klasyfikowane jako strumieniowe lub niestrumieniowe.

Streaming

Operatory przesyłania strumieniowego nie muszą odczytywać wszystkich danych źródłowych przed uzyskaniem elementów. W momencie wykonywania, w miarę jak element źródłowy jest odczytywany, operator przesyłania strumieniowego wykonuje na nim operację i zwraca go, jeśli jest to właściwe. 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 transmisji strumieniowej Odroczone wykonywanie niestrumieniowe
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average Pojedyncza wartość liczbowa
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max Pojedyncza wartość liczbowa, TSourcelub TResult?
Min Pojedyncza wartość liczbowa, TSourcelub TResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum Pojedyncza wartość liczbowa
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] tablica
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

LINQ to objects

"LINQ to Objects" odnosi się do bezpośredniego używania zapytań LINQ z dowolną kolekcją IEnumerable lub IEnumerable<T>. 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 porównaniu do 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 do iteracji wyników, elementy są zwracane w kolejności, w jakiej są 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ż