Udostępnij za pośrednictwem


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 jest int[]):

    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 na string. Zwróć uwagę na nowy typ highScoresQuery.

    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 selecttworzy 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.

Zobacz też