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:
- Uzyskaj źródło danych.
- Utwórz zapytanie.
- 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.
Ź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
, where
i 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
, , Average
Max
i 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.
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]);