C# LINQ-lekérdezések írása adatok lekérdezéséhez

A bevezető nyelvvel integrált lekérdezés (LINQ) dokumentáció legtöbb lekérdezése a LINQ deklaratív lekérdezési szintaxisával íródik. A lekérdezés szintaxisát azonban le kell fordítani a .NET közös nyelvi futtatókörnyezet (CLR) metódushívásaira a kód lefordításakor. Ezek a metódushívások meghívják a standard lekérdezési operátorokat, amelyek neve például Where, Select, GroupBy, Join, Maxés Average. Közvetlenül hívhatja meg őket a lekérdezés szintaxisa helyett a metódusszintaxis használatával.

A lekérdezési szintaxis és a metódusszintaxis szemantikailag azonos, de a lekérdezési szintaxis gyakran egyszerűbb és könnyebben olvasható. Egyes lekérdezéseket metódushívásokként kell kifejezni. Például egy metódushívással kell kifejezésre juttatnia egy lekérdezést, amely lekéri a megadott feltételnek megfelelő elemek számát. Metódushívást is kell használnia egy olyan lekérdezéshez, amely lekéri azt az elemet, amelynek a maximális értéke egy forrásütemezésben. A névtérben lévő System.Linq szabványos lekérdezési operátorok referenciadokumentációja általában metódusszintaxist használ. Ismernie kell a metódusszintaxis használatát a lekérdezésekben és a lekérdezési kifejezésekben.

Standard lekérdezési operátor-bővítménymetelyek

Az alábbi példa egy egyszerű lekérdezési kifejezést és egy metódusalapú lekérdezésként írt szemantikailag egyenértékű lekérdezést mutat be.

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

//Query syntax:
IEnumerable<int> numQuery1 =
    from num in numbers
    where num % 2 == 0
    orderby num
    select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

foreach (int i in numQuery1)
{
    Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
    Console.Write(i + " ");
}

A két példa kimenete azonos. Láthatja, hogy a lekérdezési változó típusa mindkét formában megegyezik: IEnumerable<T>.

A metódusalapú lekérdezés megértéséhez vizsgáljuk meg közelebbről. A kifejezés jobb oldalán figyelje meg, hogy a where záradék most már példánymetódusként van kifejezve az numbers objektumon, amelynek típusa IEnumerable<int>. Ha ismeri az általános IEnumerable<T> felületet, akkor tudja, hogy nincs metódusa Where . Ha azonban meghívja az IntelliSense befejezési listáját a Visual Studio IDE-ben, akkor nem csak egy Where metódust, hanem számos más metódust is láthat, például Select, SelectManyés JoinOrderby. Ezek a metódusok a szabványos lekérdezési operátorokat implementálják.

Képernyőkép az Intellisense összes szabványos lekérdezési operátorával.

Bár úgy tűnik, hogy IEnumerable<T> több metódust is tartalmaz, nem. A standard lekérdezési operátorok bővítménymeteként vannak implementálva. A bővítmények metódusai egy meglévő típust "bővítenek"; úgy hívhatók meg, mintha példánymetóták lennének a típuson. A szabványos lekérdezési operátorok kiterjeszthetők IEnumerable<T> , ezért írhat numbers.Where(...).

A bővítménymetelyek használatához irányelvekkel using léptetheti őket hatókörbe. Az alkalmazás szempontjából a bővítménymetódus és a normál példánymetódus megegyezik.

A bővítménymetelyekről további információt a Bővítménymetelyek című témakörben talál. A szabványos lekérdezési operátorokról további információt a Standard lekérdezési operátorok áttekintése (C#) című témakörben talál. Egyes LINQ-szolgáltatók, például az Entity Framework és a LINQ az XML-hez, saját szabványos lekérdezési operátorokat és bővítménymetszeteket implementálnak a többi típushoz is IEnumerable<T>.

Lambda-kifejezések

Az előző példában figyelje meg, hogy a feltételes kifejezés (num % 2 == 0) soron belüli argumentumként van átadva a Enumerable.Where metódusnak: Where(num => num % 2 == 0). Ez a beágyazott kifejezés egy lambda kifejezés. Ez egy kényelmes módja annak, hogy olyan kódot írjon, amelyet egyébként nehézkesebb formában kellene írni. Az num operátor bal oldalán található a lekérdezési kifejezésben szereplő num bemeneti változó. A fordító ki tudja következtetni a típust num , mert tudja, hogy numbers ez egy általános IEnumerable<T> típus. A lambda törzse megegyezik a lekérdezés szintaxisában vagy bármely más C#-kifejezésben vagy utasításban szereplő kifejezéssel. Tartalmazhat metódushívásokat és más összetett logikát. A visszatérési érték csak a kifejezés eredménye. Bizonyos lekérdezések csak metódusszintaxisban fejezhetők ki, és némelyikhez lambdakifejezések szükségesek. A Lambda-kifejezések hatékony és rugalmas eszközök a LINQ-eszközkészletben.

A lekérdezések kompatibilitása

Az előző kód példában a Enumerable.OrderBy metódus meghívása a pont operátorral történik a hívásban Where. Where szűrt sorozatot hoz létre, majd Orderby rendezi a Where. Mivel a lekérdezések egy IEnumerableértéket adnak vissza, a metódushívások összeláncolásával a metódusszintaxisban kell őket összefűzni. A fordító ezt az összeállítást akkor végzi el, amikor lekérdezéseket ír lekérdezések szintaxisával. Mivel egy lekérdezési változó nem tárolja a lekérdezés eredményeit, bármikor módosíthatja vagy felhasználhatja egy új lekérdezés alapjaként, még a végrehajtás után is.

Az alábbi példák néhány egyszerű LINQ-lekérdezést mutatnak be a korábban felsorolt megközelítések használatával.

Feljegyzés

Ezek a lekérdezések egyszerű memóriabeli gyűjteményeken működnek; Az alapszintaxis azonban megegyezik a LINQ entitásokra és a LINQ-ról XML-hez használt szintaxisával.

Példa – Lekérdezési szintaxis

A lekérdezési kifejezések létrehozásához a legtöbb lekérdezést lekérdezési szintaxissal kell írnia. Az alábbi példa három lekérdezési kifejezést mutat be. Az első lekérdezési kifejezés bemutatja, hogyan szűrheti vagy korlátozhatja az eredményeket feltételek where záradékkal való alkalmazásával. A forrásütemezés minden olyan elemét visszaadja, amelynek értéke 7-nél vagy 3-nál kisebb. A második kifejezés bemutatja, hogyan rendezheti a visszaadott eredményeket. A harmadik kifejezés bemutatja, hogyan csoportosíthatja az eredményeket egy kulcs alapján. Ez a lekérdezés két csoportot ad vissza a szó első betűje alapján.

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num is < 3 or > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num is < 3 or > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

A lekérdezések típusa: IEnumerable<T>. Ezek a lekérdezések az alábbi példában látható módon írhatók var :

var query = from num in numbers...

Az előző példában a lekérdezések csak akkor hajthatók végre, ha egy utasításban vagy más utasításban foreach iterálja át a lekérdezési változót.

Példa – Metódus szintaxisa

Egyes lekérdezési műveleteket metódushívásként kell kifejezni. A leggyakoribb ilyen metódusok azok a metódusok, amelyek egyszeri numerikus értékeket adnak vissza, például Sum, Max, Min, Averagestb. Ezeket a metódusokat mindig minden lekérdezésben utolsóként kell meghívni, mert egyetlen értéket adnak vissza, és nem szolgálhatnak forrásként egy további lekérdezési művelethez. Az alábbi példa egy metódushívást mutat be egy lekérdezési kifejezésben:

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

Ha a metódus rendelkezik vagy paraméterekkel rendelkezikSystem.Action, ezek az argumentumok lambda kifejezés formájában jelennek meg, ahogyan az alábbi példában látható:System.Func<TResult>

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

Az előző lekérdezésekben csak a 4. lekérdezés fut azonnal, mert egyetlen értéket ad vissza, nem pedig általános IEnumerable<T> gyűjteményt. A metódus maga vagy hasonló kódot használ foreach az érték kiszámításához.

Az előző lekérdezések mindegyike implicit beírással írható a "var" kifejezéssel, ahogyan az a következő példában látható:

// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Példa – Vegyes lekérdezés és metódus szintaxisa

Ez a példa bemutatja, hogyan használható metódusszintaxis egy lekérdezési záradék eredményein. Csak zárójelbe tegye a lekérdezési kifejezést, majd alkalmazza a pont operátort, és hívja meg a metódust. A következő példában a 7. lekérdezés azoknak a számoknak a számát adja vissza, amelyek értéke 3 és 7 között van. Általában azonban jobb, ha egy második változót használ a metódushívás eredményének tárolására. Így a lekérdezés kevésbé valószínű, hogy összekeveredik a lekérdezés eredményeivel.

// Query #7.

// Using a query expression with method syntax
var numCount1 = (
    from num in numbers1
    where num is > 3 and < 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num is > 3 and < 7
    select num;

var numCount2 = numbersQuery.Count();

Mivel a 7. lekérdezés egyetlen értéket ad vissza, nem gyűjteményt, a lekérdezés azonnal végrehajtja.

Az előző lekérdezés implicit beírással varírható az alábbiak szerint:

var numCount = (from num in numbers...

Metódusszintaxisban az alábbiak szerint írható:

var numCount = numbers.Count(n => n is > 3 and < 7);

Explicit beírással írható, az alábbiak szerint:

int numCount = numbers.Count(n => n is > 3 and < 7);

Predikátumszűrők dinamikus megadása futásidőben

Bizonyos esetekben nem tudhatja, hogy a futtatásig hány predikátumot kell alkalmaznia a where záradék forráselemeire. Több predikátumszűrő dinamikus megadásának egyik módja a Contains módszer használata, ahogyan az az alábbi példában is látható. A lekérdezés különböző eredményeket ad vissza a lekérdezés végrehajtásának értéke id alapján.

int[] ids = [111, 114, 112];

var queryNames =
    from student in students
    where ids.Contains(student.ID)
    select new
    {
        student.LastName,
        student.ID
    };

foreach (var name in queryNames)
{
    Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
    Garcia: 114
    O'Donnell: 112
    Omelchenko: 111
 */

// Change the ids.
ids = [122, 117, 120, 115];

// The query will now return different results
foreach (var name in queryNames)
{
    Console.WriteLine($"{name.LastName}: {name.ID}");
}

/* Output:
    Adams: 120
    Feng: 117
    Garcia: 115
    Tucker: 122
 */

A vezérlőfolyamat-utasítások( például if... else vagy switch) használatával választhat az előre meghatározott alternatív lekérdezések közül. Az alábbi példában egy másik where záradékot használ, studentQuery ha a futtatási idő értéke oddYear vagy .falsetrue

void FilterByYearType(bool oddYear)
{
    IEnumerable<Student> studentQuery = oddYear
        ? (from student in students
           where student.Year is GradeLevel.FirstYear or GradeLevel.ThirdYear
           select student)
        : (from student in students
           where student.Year is GradeLevel.SecondYear or GradeLevel.FourthYear
           select student);
    var descr = oddYear ? "odd" : "even";
    Console.WriteLine($"The following students are at an {descr} year level:");
    foreach (Student name in studentQuery)
    {
        Console.WriteLine($"{name.LastName}: {name.ID}");
    }
}

FilterByYearType(true);

/* Output:
    The following students are at an odd year level:
    Fakhouri: 116
    Feng: 117
    Garcia: 115
    Mortensen: 113
    Tucker: 119
    Tucker: 122
 */

FilterByYearType(false);

/* Output:
    The following students are at an even year level:
    Adams: 120
    Garcia: 114
    Garcia: 118
    O'Donnell: 112
    Omelchenko: 111
    Zabokritski: 121
 */

Null értékek kezelése lekérdezési kifejezésekben

Ez a példa bemutatja, hogyan kezelhetők a lehetséges null értékek a forrásgyűjteményekben. Egy objektumgyűjtemény, például egy IEnumerable<T> null értékű elemeket tartalmazhat. Ha egy forrásgyűjtemény olyan null elemet tartalmaz vagy tartalmaz, amelynek az értéke null, és a lekérdezés nem kezeli null az értékeket, NullReferenceException a rendszer a lekérdezés végrehajtásakor egy műveletet hajt végre.

A nullhivatkozási kivétel elkerülése érdekében a következő példában látható módon védekezhet:

var query1 =
    from c in categories
    where c != null
    join p in products on c.ID equals p?.CategoryID
    select new
    {
        Category = c.Name,
        Name = p.Name
    };

Az előző példában a záradék kiszűri a where kategóriák sorozatának összes null elemét. Ez a technika független az illesztési záradék null-ellenőrzésétől. Ebben a példában a null értékű feltételes kifejezés azért működik, mert Products.CategoryID a kifejezés típusa int?rövid.Nullable<int>

Ha egy illesztési záradékban az összehasonlító kulcsok közül csak az egyik null értékű, akkor a másikat egy null értékű típusra állíthatja a lekérdezési kifejezésben. A következő példában tegyük fel, hogy EmployeeID egy olyan oszlop, amely típusértékeket int?tartalmaz:

var query =
    from o in db.Orders
    join e in db.Employees
        on o.EmployeeID equals (int?)e.EmployeeID
    select new { o.OrderID, e.FirstName };

Az egyes példákban a equals lekérdezési kulcsszót használja a rendszer. A mintaegyezést is használhatja, amely tartalmazza a minták és is not nulla is null . Ezek a minták nem ajánlottak a LINQ-lekérdezésekben, mert előfordulhat, hogy a lekérdezésszolgáltatók nem megfelelően értelmezik az új C# szintaxist. A lekérdezésszolgáltató olyan kódtár, amely c# lekérdezési kifejezéseket fordít natív adatformátumra, például Entity Framework Core-ra. A lekérdezésszolgáltatók az System.Linq.IQueryProvider interfészt megvalósító adatforrások létrehozásához implementálják az interfészt System.Linq.IQueryable<T> .

Kivételek kezelése lekérdezési kifejezésekben

Bármely metódus meghívható egy lekérdezési kifejezés kontextusában. Ne hívjon meg olyan metódust egy lekérdezési kifejezésben, amely okozhat mellékhatásokat, például módosíthatja az adatforrás tartalmát, vagy kivételt okozhat. Ez a példa bemutatja, hogyan kerülheti el a kivételeket, ha metódusokat hív meg egy lekérdezési kifejezésben anélkül, hogy megsérti a kivételkezelés általános .NET-irányelveit. Ezek az irányelvek azt jelzik, hogy elfogadható, ha egy adott kivételt észlel, ha tisztában van azzal, hogy miért van az adott kontextusban. További információ: Ajánlott eljárások a kivételekhez.

Az utolsó példa bemutatja, hogyan kezelheti azokat az eseteket, amikor kivételt kell tennie egy lekérdezés végrehajtása során.

Az alábbi példa bemutatja, hogyan helyezheti át a kivételkezelő kódot egy lekérdezési kifejezésen kívül. Ez az újrabontás csak akkor lehetséges, ha a metódus nem függ a lekérdezés helyi változóitól. Egyszerűbb kezelni a lekérdezési kifejezésen kívüli kivételeket.

// A data source that is very likely to throw an exception!
IEnumerable<int> GetData() => throw new InvalidOperationException();

// DO THIS with a datasource that might
// throw an exception.
IEnumerable<int>? dataSource = null;
try
{
    dataSource = GetData();
}
catch (InvalidOperationException)
{
    Console.WriteLine("Invalid operation");
}

if (dataSource is not null)
{
    // If we get here, it is safe to proceed.
    var query =
        from i in dataSource
        select i * i;

    foreach (var i in query)
    {
        Console.WriteLine(i.ToString());
    }
}

Az előző példában szereplő catch (InvalidOperationException) blokkban kezelje (vagy ne kezelje) a kivételt az alkalmazásnak megfelelő módon.

Bizonyos esetekben a lekérdezésen belüli kivételre adott legjobb válasz az lehet, ha azonnal leállítja a lekérdezés végrehajtását. Az alábbi példa bemutatja, hogyan kezelhetők a lekérdezés törzsében esetlegesen megjelenő kivételek. Tegyük fel, hogy ez SomeMethodThatMightThrow olyan kivételt okozhat, amely megköveteli a lekérdezés végrehajtásának leállítását.

A try blokk a foreach hurkot, nem magát a lekérdezést csatolja. A foreach ciklus az a pont, ahol a lekérdezés végrehajtásra kerül. A rendszer futásidejű kivételeket ad ki a lekérdezés végrehajtásakor. Ezért ezeket a foreach hurkokban kell kezelni.

// Not very useful as a general purpose method.
string SomeMethodThatMightThrow(string s) =>
    s[4] == 'C' ?
        throw new InvalidOperationException() :
        @"C:\newFolder\" + s;

// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];

// Demonstration query that throws.
var exceptionDemoQuery =
    from file in files
    let n = SomeMethodThatMightThrow(file)
    select n;

try
{
    foreach (var item in exceptionDemoQuery)
    {
        Console.WriteLine($"Processing {item}");
    }
}
catch (InvalidOperationException e)
{
    Console.WriteLine(e.Message);
}

/* Output:
    Processing C:\newFolder\fileA.txt
    Processing C:\newFolder\fileB.txt
    Operation is not valid due to the current state of the object.
 */

Ne felejtse el elkapni az esetleges kivételeket, és/vagy végezze el a szükséges törlést egy finally blokkban.

Lásd még