Podstawowe informacje o wyrażeniu zapytania
W tym artykule przedstawiono podstawowe pojęcia związane z wyrażeniami zapytań w języku C#.
Co to jest zapytanie i co ono robi?
Zapytanie to zestaw instrukcji opisujących, jakie dane mają zostać pobrane z danego źródła danych (lub źródeł) oraz jaki kształt i organizacja powinny mieć zwrócone dane. Zapytanie różni się od wyników, które generuje.
Ogólnie rzecz biorąc, dane źródłowe są zorganizowane logicznie jako sekwencja elementów tego samego rodzaju. Na przykład tabela bazy danych SQL zawiera sekwencję wierszy. W pliku XML istnieje "sekwencja" elementów XML (chociaż elementy XML są zorganizowane hierarchicznie w strukturze drzewa). Kolekcja w pamięci zawiera sekwencję obiektów.
Z punktu widzenia aplikacji konkretny typ i struktura oryginalnych danych źródłowych nie są ważne. Aplikacja zawsze widzi dane źródłowe jako kolekcję IEnumerable<T> lub IQueryable<T>. Na przykład w linQ to XML dane źródłowe są widoczne jako IEnumerable
<XElement>.
Biorąc pod uwagę tę sekwencję źródłową, zapytanie może wykonać jedną z trzech czynności:
Pobierz podzestaw elementów, aby utworzyć nową sekwencję bez modyfikowania poszczególnych elementów. Zapytanie może następnie sortować lub grupować zwróconą sekwencję na różne sposoby, jak pokazano w poniższym przykładzie (załóżmy, że
scores
jestint[]
):IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
Pobierz sekwencję elementów, tak jak w poprzednim przykładzie, ale przekształć je w nowy typ obiektu. Na przykład zapytanie może pobierać tylko nazwy rodzin z niektórych rekordów klientów w źródle danych. Może też pobrać pełny rekord, a następnie użyć go do konstruowania innego typu obiektu w pamięci, a nawet danych XML przed wygenerowaniem sekwencji wyników końcowych. W poniższym przykładzie przedstawiono projekcję z
int
nastring
. Zwróć uwagę na nowy typhighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
Pobierz pojedynczą wartość dotyczącą danych źródłowych, taką jak:
Liczba elementów pasujących do określonego warunku.
Element, który ma największą lub najmniejszą wartość.
Pierwszy element zgodny z warunkiem lub suma określonych wartości w określonym zestawie elementów. Na przykład następujące zapytanie zwraca liczbę wyników większych niż 80 z tablicy
scores
całkowitej:var highScoreCount = ( from score in scores where score > 80 select score ).Count();
W poprzednim przykładzie zwróć uwagę na użycie nawiasów wokół wyrażenia zapytania przed wywołaniem metody Enumerable.Count. Możesz również użyć nowej zmiennej do przechowywania konkretnego wyniku.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var scoreCount = highScoresQuery3.Count();
W poprzednim przykładzie zapytanie jest wykonywane w wywołaniu do Count
, ponieważ Count
musi iterować po wynikach, aby określić liczbę elementów zwróconych przez highScoresQuery
.
Co to jest wyrażenie zapytania?
Wyrażenie zapytania jest zapytaniem wyrażonym w składni zapytania. Wyrażenie zapytania jest konstrukcją języka pierwszej klasy. Jest to tak samo jak każde inne wyrażenie i może być używane w dowolnym kontekście, w którym wyrażenie języka C# jest prawidłowe. Wyrażenie zapytania składa się z zestawu klauzul napisanych w składni deklaratywnej podobnej do języka SQL lub XQuery. Każda klauzula z kolei zawiera co najmniej jedno wyrażenie języka C#, a te wyrażenia mogą być wyrażeniem zapytania lub zawierać wyrażenie zapytania.
Wyrażenie zapytania musi rozpoczynać się od klauzuli i musi kończyć się klauzulą wybierz lub klauzulą grupy . Między pierwszą klauzulą from
a ostatnią klauzulą select
lub group
może zawierać co najmniej jedną z następujących klauzul opcjonalnych: gdzie, orderby, join, let, a nawet inne z klauzul. Możesz również użyć słowa kluczowego into
, aby włączyć wynik klauzuli join
lub group
, aby służyć jako źródło większej liczby klauzul zapytania w tym samym wyrażeniu zapytania.
Zmienna kwerendy
W linQ zmienna kwerendy jest dowolną zmienną, która przechowuje zapytanie zamiast wyników zapytania. W szczególności zmienna kwerendy jest zawsze typem wyliczalnym, który generuje sekwencję elementów przy iteracji w instrukcji foreach
lub przy bezpośrednim wywołaniu metody IEnumerator.MoveNext().
Notatka
Przykłady w tym artykule korzystają z następującego źródła danych i przykładowych danych.
record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];
static readonly Country[] countries = [
new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];
Poniższy przykład kodu przedstawia proste wyrażenie zapytania z jednym źródłem danych, jedną klauzulą filtrowania, jedną klauzulą porządkowania i bez przekształcenia elementów źródłowych. Klauzula select
kończy zapytanie.
// Data source.
int[] scores = [90, 71, 82, 93, 75, 82];
// Query Expression.
IEnumerable<int> scoreQuery = //query variable
from score in scores //required
where score > 80 // optional
orderby score descending // optional
select score; //must end with select or group
// Execute the query to produce the results
foreach (var testScore in scoreQuery)
{
Console.WriteLine(testScore);
}
// Output: 93 90 82 82
W poprzednim przykładzie scoreQuery
jest zmienną zapytania, która jest czasami nazywana tylko zapytania . Zmienna kwerendy nie przechowuje żadnych rzeczywistych danych wynikowych, które są generowane w pętli foreach
. A po wykonaniu instrukcji foreach
wyniki zapytania nie są zwracane za pośrednictwem zmiennej zapytania scoreQuery
. Zamiast tego są one zwracane za pośrednictwem zmiennej iteracji testScore
. Zmienna scoreQuery
może być iterowana w drugiej pętli foreach
. Generuje on te same wyniki, o ile ani nie został zmodyfikowany, ani źródło danych.
Zmienna kwerendy może przechowywać zapytanie wyrażone w składni zapytania lub składni metody albo kombinację tych dwóch. W poniższych przykładach zarówno queryMajorCities
, jak i queryMajorCities2
są zmiennymi zapytania:
City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000)
];
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 30_000_000
select city;
// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 30_000_000);
// Execute the query to produce the results
foreach (City city in queryMajorCities2)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
Z drugiej strony w poniższych dwóch przykładach pokazano zmienne, które nie są zmiennymi zapytania, mimo że każda z nich jest inicjowana za pomocą zapytania. Nie są to zmienne zapytań, ponieważ przechowują wyniki:
var highestScore = (
from score in scores
select score
).Max();
// or split the expression
IEnumerable<int> scoreQuery =
from score in scores
select score;
var highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
var largeCitiesList = (
from country in countries
from city in country.Cities
where city.Population > 10000
select city
).ToList();
// or split the expression
IEnumerable<City> largeCitiesQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
var largeCitiesList2 = largeCitiesQuery.ToList();
Jawne i niejawne wpisywanie zmiennych zapytania
Ta dokumentacja zwykle udostępnia jawny typ zmiennej zapytania, aby pokazać relację typu między zmienną kwerendy a klauzulą select . Można jednak również użyć słowa kluczowego var w celu poinstruowania kompilatora, aby wywnioskował typ zmiennej kwerendy (lub innej zmiennej lokalnej) w czasie kompilacji. Na przykład przykład zapytania, który został przedstawiony wcześniej w tym artykule, można również wyrazić przy użyciu niejawnego pisania:
var queryCities =
from city in cities
where city.Population > 100000
select city;
W poprzednim przykładzie użycie var jest opcjonalne.
queryCities
jest IEnumerable<City>
, czy jawnie, czy niejawnie typowany.
Uruchamianie wyrażenia zapytania
Wyrażenie zapytania musi zaczynać się od klauzuli from
. Określa źródło danych wraz ze zmienną zakresu. Zmienna zakresu reprezentuje każdy kolejny element w sekwencji źródłowej, gdy sekwencja źródłowa jest przechodzona. Zmienna zakresu jest silnie typizowana na podstawie typu elementów w źródle danych. W poniższym przykładzie, ponieważ countries
jest tablicą obiektów Country
, zmienna zakresu jest również typowana jako Country
. Ponieważ zmienna zakresu jest silnie typizowana, możesz użyć operatora kropki, aby uzyskać dostęp do dowolnych członków typu.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 20 //sq km
select country;
Zmienna zakresu pozostaje w zasięgu, dopóki zapytanie nie zostanie zakończone średnikiem lub klauzulą kontynuacji .
Wyrażenie zapytania może zawierać wiele from
klauzul. Stosuj więcej klauzul from
, gdy każdy element w sekwencji źródłowej jest kolekcją lub zawiera kolekcję. Załóżmy na przykład, że masz kolekcję obiektów Country
, z których każda zawiera kolekcję obiektów City
o nazwie Cities
. Aby wysłać zapytanie do obiektów City
w każdym Country
, użyj dwóch klauzul from
, jak pokazano poniżej:
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
Aby uzyskać więcej informacji, zobacz z klauzuli.
Kończenie wyrażenia zapytania
Wyrażenie zapytania musi kończyć się klauzulą group
lub klauzulą select
.
Klauzula grupowa
Użyj klauzuli group
, aby utworzyć sekwencję grup zorganizowanych według określonego klucza. Klucz może być dowolnym typem danych. Na przykład poniższe zapytanie tworzy sekwencję grup, która zawiera co najmniej jeden obiekt Country
i którego klucz jest typem char
z wartością będącą pierwszą literą nazw krajów.
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Aby uzyskać więcej informacji na temat grupowania, zobacz klauzulę grupowania.
klauzula SELECT
Użyj klauzuli select
, aby utworzyć wszystkie inne typy sekwencji. Prosta klauzula select
po prostu tworzy sekwencję obiektów tego samego typu co obiekty znajdujące się w źródle danych. W tym przykładzie źródło danych zawiera obiekty Country
. Klauzula orderby
po prostu sortuje elementy w nową kolejność, a klauzula select
tworzy sekwencję ponownie uporządkowanych obiektów Country
.
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
Klauzula select
może służyć do przekształcania danych źródłowych w sekwencje nowych typów. Ta transformacja jest również określana jako projekcja . W poniższym przykładzie klauzula select
tworzy projekcję na sekwencję typów anonimowych, która zawiera tylko podzbiór pól w oryginalnym elemencie. Nowe obiekty są inicjowane przy użyciu inicjatora obiektów.
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
W tym przykładzie var
jest wymagany, ponieważ zapytanie generuje typ anonimowy.
Aby uzyskać więcej informacji na temat wszystkich sposobów przekształcania danych źródłowych za pomocą klauzuli select
, zobacz klauzulę select.
Kontynuacje z do
Możesz użyć słowa kluczowego into
w klauzuli select
lub group
, aby utworzyć tymczasowy identyfikator, który przechowuje zapytanie. Użyj klauzuli into
, jeśli musisz wykonywać dodatkowe operacje zapytań na kwerendzie po operacji grupowania lub wybierania. W poniższym przykładzie countries
są grupowane według populacji w zakresie 10 milionów. Po utworzeniu tych grup więcej klauzul filtruje niektóre grupy, a następnie sortuje grupy w kolejności rosnącej. Aby wykonać te dodatkowe operacje, wymagana jest kontynuacja reprezentowana przez countryGroup
.
// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
from country in countries
let percentile = (int)country.Population / 1_000
group country by percentile into countryGroup
where countryGroup.Key >= 20
orderby countryGroup.Key
select countryGroup;
// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
Console.WriteLine(grouping.Key);
foreach (var country in grouping)
{
Console.WriteLine(country.Name + ":" + country.Population);
}
}
Aby uzyskać więcej informacji, zobacz into
.
Filtrowanie, porządkowanie i łączenie
Między klauzulą początkową from
a końcową klauzulą select
lub group
wszystkie inne klauzule (where
, join
, orderby
, from
, let
) są opcjonalne. Każda z klauzul opcjonalnych może być używana zero razy lub wiele razy w treści zapytania.
klauzula 'where'
Użyj klauzuli where
, aby odfiltrować elementy z danych źródłowych na podstawie co najmniej jednego wyrażenia predykatu. Klauzula where
w poniższym przykładzie ma jeden predykat z dwoma warunkami.
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
Aby uzyskać więcej informacji, zobacz klauzulę , w której.
Klauzula orderby
Użyj klauzuli orderby
, aby posortować wyniki w kolejności rosnącej lub malejącej. Można również określić dodatkowe porządki sortowania. Poniższy przykład wykonuje sortowanie podstawowe dla obiektów country
przy użyciu właściwości Area
. Następnie wykonuje sortowanie pomocnicze przy użyciu właściwości Population
.
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
Słowo kluczowe ascending
jest opcjonalne; jest to domyślna kolejność sortowania, jeśli nie określono kolejności. Aby uzyskać więcej informacji, zobacz klauzulę ORDER BY
Klauzula łączenia
Użyj klauzuli join
, aby skojarzyć i/lub połączyć elementy z jednego źródła danych z elementami z innego źródła danych na podstawie porównania równości między określonymi kluczami w każdym elemecie. W linQ operacje sprzężenia są wykonywane na sekwencjach obiektów, których elementy są różnymi typami. Po połączeniu dwóch sekwencji należy użyć instrukcji select
lub group
, aby określić, który element ma być przechowywany w sekwencji danych wyjściowych. Można również użyć typu anonimowego, aby połączyć właściwości z każdego zestawu skojarzonych elementów w nowy typ sekwencji danych wyjściowych. Poniższy przykład kojarzy obiekty prod
, których właściwość Category
pasuje do jednej z kategorii w tablicy ciągów categories
. Produkty, których Category
nie pasują do żadnego ciągu w categories
, są filtrowane. Instrukcja select
projektuje nowy typ, którego właściwości są pobierane zarówno z cat
, jak i prod
.
var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};
Można również wykonać łączenie grup, poprzez przechowywanie wyników operacji join
w zmiennej tymczasowej przy użyciu słowa kluczowego into
. Aby uzyskać więcej informacji, zobacz klauzulę łączenia .
Klauzula let
Użyj klauzuli let
do przechowywania wyniku wyrażenia, takiego jak wywołanie metody, w nowej zmiennej zakresu. W poniższym przykładzie zmienna zakresu firstName
przechowuje pierwszy element tablicy ciągów zwracanych przez Split
.
string[] names = ["Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia"];
IEnumerable<string> queryFirstNames =
from name in names
let firstName = name.Split(' ')[0]
select firstName;
foreach (var s in queryFirstNames)
{
Console.Write(s + " ");
}
//Output: Svetlana Claire Sven Cesar
Aby uzyskać więcej informacji, zobacz klauzulę let .
Podzapytania w wyrażeniu zapytania
Klauzula zapytania może zawierać wyrażenie zapytania, które jest czasami określane jako podzapytywanie . Każde podzapytanie rozpoczyna się od własnej klauzuli from
, która niekoniecznie wskazuje na to samo źródło danych co pierwsza klauzula from
. Na przykład następujące zapytanie przedstawia wyrażenie zapytania, które jest używane w instrukcji select w celu pobrania wyników operacji grupowania.
var queryGroupMax =
from student in students
group student by student.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.ExamScores.Average()
).Max()
};
Aby uzyskać więcej informacji, zobacz Wykonaj podzapytanie w operacji grupowania.