Dela via


Grunderna i frågeuttryck

Den här artikeln beskriver grundläggande begrepp som rör frågeuttryck i C#.

Vad är en fråga och vad gör den?

En fråga är en uppsättning instruktioner som beskriver vilka data som ska hämtas från en viss datakälla (eller källor) och vilken form och organisation de returnerade data ska ha. En fråga skiljer sig från de resultat som den producerar.

Vanligtvis ordnas källdata logiskt som en sekvens med element av samma slag. En SQL-databastabell innehåller till exempel en sekvens med rader. I en XML-fil finns det en "sekvens" av XML-element (även om XML-element ordnas hierarkiskt i en trädstruktur). En minnesintern samling innehåller en sekvens med objekt.

Från ett programs synvinkel är den specifika typen och strukturen för de ursprungliga källdata inte viktig. Programmet ser alltid källdata som en IEnumerable<T> eller IQueryable<T> samling. I LINQ till XML visas till exempel källdata som en IEnumerable<XElement>.

Med tanke på den här källsekvensen kan en fråga göra något av tre saker:

  • Hämta en delmängd av elementen för att skapa en ny sekvens utan att ändra de enskilda elementen. Frågan kan sedan sortera eller gruppera den returnerade sekvensen på olika sätt, som du ser i följande exempel (anta att scores är en int[]):

    IEnumerable<int> highScoresQuery =
        from score in scores
        where score > 80
        orderby score descending
        select score;
    
  • Hämta en sekvens med element som i föregående exempel men transformera dem till en ny typ av objekt. En fråga kan till exempel bara hämta familjenamnen från vissa kundposter i en datakälla. Eller så kan den hämta den fullständiga posten och sedan använda den för att konstruera en annan minnesintern objekttyp eller till och med XML-data innan den slutliga resultatsekvensen genereras. I följande exempel visas en projektion från en int till en string. Observera den nya typen av highScoresQuery.

    IEnumerable<string> highScoresQuery2 =
        from score in scores
        where score > 80
        orderby score descending
        select $"The score is {score}";
    
  • Hämta ett singleton-värde om källdata, till exempel:

    • Antalet element som matchar ett visst villkor.

    • Det element som har det största eller minsta värdet.

    • Det första elementet som matchar ett villkor eller summan av vissa värden i en angiven uppsättning element. Följande fråga returnerar till exempel antalet poäng som är större än 80 från scores heltalsmatris:

      var highScoreCount = (
          from score in scores
          where score > 80
          select score
      ).Count();
      

      I föregående exempel bör du notera användningen av parenteser runt frågeuttrycket innan anropet till metoden Enumerable.Count. Du kan också använda en ny variabel för att lagra det konkreta resultatet.

      IEnumerable<int> highScoresQuery3 =
          from score in scores
          where score > 80
          select score;
      
      var scoreCount = highScoresQuery3.Count();
      

I föregående exempel körs frågan i anropet till Counteftersom Count måste iterera över resultaten för att fastställa antalet element som returneras av highScoresQuery.

Vad är ett frågeuttryck?

Ett frågeuttryck är en fråga som uttrycks i frågesyntaxen. Ett frågeuttryck är en förstklassig språkkonstruktion. Det är precis som alla andra uttryck och kan användas i alla sammanhang där ett C#-uttryck är giltigt. Ett frågeuttryck består av en uppsättning satser skrivna i en deklarativ syntax som liknar SQL eller XQuery. Varje sats innehåller i sin tur ett eller flera C#-uttryck, och dessa uttryck kan i sig vara antingen ett frågeuttryck eller innehålla ett frågeuttryck.

Ett frågeuttryck måste börja med en från-sats och måste avslutas med en select eller grupp-sats. Mellan den första from-satsen och den sista select- eller group-satsen kan den innehålla en eller flera av dessa valfria satser: där, orderby, join, låta och till och med en annan från-satser. Du kan också använda nyckelordet into för att aktivera resultatet av en join- eller group-sats för att fungera som källa för fler frågesatser i samma frågeuttryck.

Frågevariabel

I LINQ är en frågevariabel en variabel som lagrar en fråga i stället för resultat för en fråga. Mer specifikt är en frågevariabel alltid en uppräkningsbar typ som genererar en sekvens med element när den itereras över i en foreach-instruktion eller ett direktanrop till dess IEnumerator.MoveNext()-metod.

Not

Exempel i den här artikeln använder följande datakälla och exempeldata.

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)])
];

I följande kodexempel visas ett enkelt frågeuttryck med en datakälla, en filtreringssats, en beställningssats och ingen transformering av källelementen. Satsen select avslutar frågan.

// 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

I föregående exempel är scoreQuery en frågevariabel, som ibland bara kallas för en fråga. Frågevariabeln lagrar inga faktiska resultatdata, som skapas i foreach-loopen. Och när foreach-instruktionen körs returneras inte frågeresultatet via frågevariabeln scoreQuery. I stället returneras de via iterationsvariabeln testScore. Variabeln scoreQuery kan itereras i en andra foreach-loop. Den ger samma resultat så länge varken den eller datakällan har ändrats.

En frågevariabel kan lagra en fråga som uttrycks i frågesyntax eller metodsyntax, eller en kombination av de två. I följande exempel är både queryMajorCities och queryMajorCities2 frågevariabler:

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 }

Å andra sidan visar följande två exempel variabler som inte är frågevariabler trots att var och en initieras med en fråga. De är inte frågevariabler eftersom de lagrar resultat:

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();

Explicit och implicit typning av frågevariabler

Den här dokumentationen innehåller vanligtvis den explicita typen av frågevariabel för att visa typrelationen mellan frågevariabeln och select-satsen. Du kan dock också använda nyckelordet var för att instruera kompilatorn att härleda typen av en frågevariabel (eller någon annan lokal variabel) vid kompileringstillfället. Frågeexemplet som visades tidigare i den här artikeln kan till exempel också uttryckas med implicit inmatning:

var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

I föregående exempel är användningen av var valfri. queryCities är en IEnumerable<City>, oavsett om den är implicit eller explicit.

Starta ett frågeuttryck

Ett frågeuttryck måste börja med en from-sats. Den anger en datakälla tillsammans med en intervallvariabel. Intervallvariabeln representerar varje efterföljande element i källsekvensen när källsekvensen bläddras igenom. Omfångsvariabeln är starkt typad beroende på typen av element i datakällan. I följande exempel, eftersom countries är en matris med Country objekt, skrivs även intervallvariabeln som Country. Eftersom intervallvariabeln är starkt typad kan du använda punktoperatorn för att komma åt alla tillgängliga medlemmar av typen.

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 20 //sq km
    select country;

Intervallvariabeln är giltig inom omfånget tills frågan avslutas antingen med ett semikolon eller med en fortsättnings-sats.

Ett frågeuttryck kan innehålla flera from-satser. Använd fler from-satser när varje element i källsekvensen i sig är en samling eller innehåller en samling. Anta till exempel att du har en samling Country objekt som var och en innehåller en samling City objekt med namnet Cities. Om du vill köra frågor mot City objekt i varje Countryanvänder du två from-satser som visas här:

IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

Mer information finns i från satsen.

Avsluta ett frågeuttryck

Ett frågeuttryck måste sluta med antingen en group-sats eller en select-sats.

Gruppsatsen

Använd satsen group för att skapa en sekvens med grupper ordnade efter en nyckel som du anger. Nyckeln kan vara vilken datatyp som helst. Följande fråga skapar till exempel en sekvens med grupper som innehåller ett eller flera Country objekt och vars nyckel är en char typ där värdet är den första bokstaven i ländernas namn.

var queryCountryGroups =
    from country in countries
    group country by country.Name[0];

Mer information om gruppering finns i gruppsatsen.

SELECT-sats

Använd satsen select för att skapa alla andra typer av sekvenser. En enkel select-sats skapar bara en sekvens av samma typ av objekt som objekten som finns i datakällan. I det här exemplet innehåller datakällan Country objekt. Satsen orderby sorterar bara elementen i en ny ordning och select-satsen genererar en sekvens av de omsorterade Country objekten.

IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;

Satsen select kan användas för att omvandla källdata till sekvenser av nya typer. Denna transformation kallas också för en projektion . I följande exempel projicerar select-satsen en sekvens med anonyma typer som endast innehåller en delmängd av fälten i det ursprungliga elementet. De nya objekten initieras med hjälp av en objektinitierare.

var queryNameAndPop =
    from country in countries
    select new
    {
        Name = country.Name,
        Pop = country.Population
    };

Så i det här exemplet krävs var eftersom frågan genererar en anonym typ.

Mer information om alla sätt som en select-sats kan användas för att transformera källdata finns i select-sats.

Fortsättningar med i

Du kan använda nyckelordet into i en select- eller group-sats för att skapa en tillfällig identifierare som lagrar en fråga. Använd into-satsen när du måste utföra extra frågeåtgärder på en fråga efter en grupperings- eller select-åtgärd. I följande exempel grupperas countries efter population i intervall på 10 miljoner. När dessa grupper har skapats filtrerar fler satser bort vissa grupper och sorterar sedan grupperna i stigande ordning. För att utföra dessa extra åtgärder krävs fortsättningen som representeras av 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);
    }
}

Mer information finns i into.

Filtrering, ordning och anslutning

Mellan startsatsen from och slutsatsen select eller group är alla andra satser (where, join, orderby, from, let) valfria. Valfria satser kan användas noll gånger eller flera gånger i en frågetext.

where-villkoret

Använd satsen where för att filtrera bort element från källdata baserat på ett eller flera predikatuttryck. Satsen where i följande exempel har ett predikat med två villkor.

IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population is < 15_000_000 and > 10_000_000
    select city;

Mer information finns i where-satsen.

Orderby-klausulen

Använd satsen orderby för att sortera resultatet i antingen stigande eller fallande ordning. Du kan också ange sekundära sorteringsordningar. I följande exempel utförs en primär sortering på country objekt med hjälp av egenskapen Area. Den utför sedan en sekundär sortering med hjälp av egenskapen Population.

IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area, country.Population descending
    select country;

Nyckelordet ascending är valfritt. det är standardsorteringsordningen om ingen ordning har angetts. Mer information finns i orderby-klausul.

Kopplingsuttrycket

Använd join-satsen för att associera och/eller kombinera element från en datakälla med element från en annan datakälla baserat på en likhetsjämförelse mellan angivna nycklar i varje element. I LINQ utförs kopplingsåtgärder på sekvenser av objekt vars element är olika typer. När du har sammanfogat två sekvenser måste du använda en select- eller group-instruktion för att ange vilket element som ska lagras i utdatasekvensen. Du kan också använda en anonym typ för att kombinera egenskaper från varje uppsättning associerade element till en ny typ för utdatasekvensen. I följande exempel associeras prod objekt vars Category egenskap matchar en av kategorierna i categories strängmatris. Produkter vars Category inte matchar någon sträng i categories filtreras bort. select-instruktionen projicerar en ny typ vars egenskaper hämtas från både cat och prod.

var categoryQuery =
    from cat in categories
    join prod in products on cat equals prod.Category
    select new
    {
        Category = cat,
        Name = prod.Name
    };

Du kan också utföra en gruppkoppling genom att lagra resultatet av den join åtgärden i en tillfällig variabel med hjälp av nyckelordet into. Mer information finns i kopplingssatsen.

Let-satsen

Använd let-satsen för att lagra resultatet av ett uttryck, till exempel ett metodanrop, i en ny intervallvariabel. I följande exempel lagrar intervallvariabeln firstName det första elementet i matrisen med strängar som returneras av 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

För mer information, se let-satsen.

Underfrågor i ett frågeuttryck

En frågesats kan i sig innehålla ett frågeuttryck som ibland kallas för en underfråga. Varje underfråga börjar med en egen from-sats som inte nödvändigtvis pekar på samma datakälla i den första from-satsen. Följande fråga visar till exempel ett frågeuttryck som används i select-instruktionen för att hämta resultatet av en grupperingsåtgärd.

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()
    };

För mer information, se Utför en underfråga på en grupperingsåtgärd.

Se även