Megosztás a következőn keresztül:


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 szintaxist használja. A C#-fordító metódushívásokra fordítja le a lekérdezési szintaxist. Ezek a metódushívások a szabványos lekérdezési operátorokat implementálják. Olyan neveik vannak, mint Wherea , Select, GroupBy, Join, Maxés Average. A lekérdezési szintaxis helyett közvetlenül metódusszintaxissal hívhatja meg őket.

A lekérdezési szintaxis és a metódusszintaxis szemantikailag azonos, de a lekérdezési szintaxis gyakran egyszerűbb és könnyebben olvasható. Néhány lekérdezést metódushívásként kell kifejeznie. 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. Ismerje meg, hogyan használható a metódusszintaxis 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. A lekérdezési változó típusa mindkét formában ugyanaz: IEnumerable<T>.

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énytagok egy meglévő típust "kiterjesztenek"; úgy hívhatók meg, mintha a típus tagjai lennének. A szabványos lekérdezési operátorok kiterjesztik a IEnumerable<T>, ezért írhat numbers.Where(...). Mielőtt meghívja őket, using irányelvekkel bővítheti a bővítményeket a hatókörben.

További információ a bővítménytagokról: Bővítménytagok. 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énytagokat implementálnak más típusok esetében is IEnumerable<T>.

Lambda-kifejezések

Az előző példában a feltételes kifejezés (num % 2 == 0) beágyazott argumentumként lesz átadva a Enumerable.Where metódusnak: Where(num => num % 2 == 0). Ez a beágyazott kifejezés 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 a kifejezés eredménye. Bizonyos lekérdezéseket csak metódusszintaxisban fejezhet ki, és néhány lekérdezéshez 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ő példakódban a Enumerable.OrderBy metódust a pont operátor használatával hívjuk meg a Wherehívásán. 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 alapvető LINQ-lekérdezést mutatnak be a korábban felsorolt megközelítések használatával.

Feljegyzés

Ezek a lekérdezések memórián belüli gyűjteményeken működnek; a szintaxis azonban megegyezik a LINQ entitásokhoz és a LINQ-ról XML-hez használt szintaxisával.

Példa – lekérdezési szintaxis

A legtöbb lekérdezést lekérdezési szintaxissal írja meg, hogy létrehozza a lekérdezési kifejezéseket. 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

Néhány lekérdezési műveletet metódushívásként kell kifejeznie. 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 minden lekérdezésben utoljára hívhatja meg, mert egyetlen értéket adnak vissza, és nem szolgálhatnak forrásként további lekérdezési műveletekhez. 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 System.Action paraméterekkel rendelkezikSystem.Func<TResult>, adja meg ezeket az argumentumokat lambda kifejezés formájában, az alábbi példában látható módon:

// 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, és nem á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éseket implicit beírással varis megírhatja az alábbi példában látható módon:

// 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. Csatolja a lekérdezési kifejezést zárójelekbe, 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.

// 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ést implicit beírással varis megírhatja az alábbiak szerint:

var numCount = (from num in numbers...

A metódus szintaxisa az alábbiak szerint írható:

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

Megírhatod explicit típusjelölést használva, 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
 */

Feljegyzés

Ez a példa a következő adatforrást és -adatokat használja:

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

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 studentQuery záradékot használ, where ha a futtatási idő értéke oddYear vagy .truefalse

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.

Az alábbi példa az alábbi típusokat és statikus adattömböket használja:

record Product(string Name, int CategoryID);
record Category(string Name, int ID);
static Category?[] categories =
[
    new ("brass", 1),
    null,
    new ("winds", 2),
    default,
    new ("percussion", 3)
];

static Product?[] products =
[
    new Product("Trumpet", 1),
    new Product("Trombone", 1),
    new Product("French Horn", 1),
    null,
    new Product("Clarinet", 2),
    new Product("Flute", 2),
    null,
    new Product("Cymbal", 3),
    new Product("Drum", 3)
];

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 nulla is not 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ármilyen metódust 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. Így csak akkor lehet újrabontást elvégezni, 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 kezelje őket a foreach hurokban.

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

A várt kivételt észlelheti, és elvégezheti a szükséges törlést egy finally blokkban.

Lásd még