Gå til indholdet

Videre med LINQ

LINQ (Language Integrated Query) er en kraftfuld og fleksibel del af C#, som giver dig mulighed for at arbejde med data på en intuitiv og effektiv måde. Efter at have fået et grundlæggende kendskab til LINQ, er det nu tid til at dykke lidt dybere ned i nogle mere avancerede aspekter.

Information til undervisere

Dette kapitel er en viderebygning på LINQ kapitlet og forudsætter at læseren har et grundlæggende kendskab til LINQ. Kapitlet kan bruges som en opfølgning på det første kapitel, eller som en del af en længere undervisningsforløb om LINQ.

IQueryable og IEnumerable i C#

I C# er IQueryable og IEnumerable to vigtige interfaces, der anvendes i LINQ for at arbejde med samlinger af data. Du vil høre om dem hele tiden så det er vigtigt at forstå forskellene mellem dem.

Den korte version

Valget mellem IEnumerable og IQueryable afhænger af din applikations behov:

  • Brug IEnumerable, når du arbejder med in-memory samlinger.
  • Brug IQueryable, når du arbejder med komplekse forespørgsler mod en eksempelvis en database, hvor du ønsker at udnytte database serverens kapacitet til at behandle data.

IEnumerable

IEnumerable er et interface, der er en del af System.Collections-namespace. Det bruges til at iterere over en samling af objekter i hukommelsen. Når du bruger IEnumerable, udføres alle operationer, såsom filtrering og sortering, i applikationens hukommelse. Det betyder, at hele datasættet skal være indlæst i hukommelsen, før du kan arbejde med det.

  • Bruges primært med in-memory samlinger som arrays, lister osv.
  • LINQ til objekter anvender IEnumerable til at udføre forespørgsler.
  • Når du anvender LINQ-forespørgsler på IEnumerable, returneres der et nyt IEnumerable objekt, der indeholder resultatet.
  • Hvert kald til en LINQ-metode som Where eller SelectIEnumerable returnerer et nyt IEnumerable objekt, og forespørgslen evalueres “lazy”, hvilket betyder, at den kun evalueres, når du itererer over resultatsættet.

Eksempel på brug af IEnumerable:

var personer = new List<Person>
{
    new Person { Navn = "Hans", Alder = 25 },
    new Person { Navn = "Grethe", Alder = 35 },
    new Person { Navn = "Jens", Alder = 42 },
    new Person { Navn = "Lise", Alder = 28 }
};

// Filtrer personer ældre end 30 år
IEnumerable<Person> filtreredePersoner = personer.Where(p => p.Alder > 30);

// Udskriver de filtrerede personer
foreach (var person in filtreredePersoner)
{
    Console.WriteLine($"{person.Navn} er {person.Alder} år gammel");
}

class Person
{
    public string Navn { get; set; }
    public int Alder { get; set; }
}

Info

Se eventuelt aloisdg/awesome-linq for yderligere ressourcer.

IQueryable

IQueryable er en del af System.Linq-namespace og er designet til at arbejde med datakilder, der ligger uden for applikationen, såsom en database. Når du bruger IQueryable, konstrueres forespørgslen i form af en Expression Tree, som så oversættes til en passende forespørgselsformat for datakilden, for eksempel SQL for en SQL Server database.

  • Bruges med datakilder som databases via ORM-frameworks som Entity Framework.
  • Tillader udførelse af forespørgsler på serveren, hvilket kan være mere effektivt, især for store datasæt.
  • LINQ til Entities (eller lignende teknologier) anvender IQueryable til at bygge og udføre forespørgsler.
  • Når en LINQ-forespørgsel anvendes på IQueryable, konstrueres en Expression Tree, som senere oversættes til den specifikke forespørgselssprog for datakilden.

Eksempel på brug af IQueryable:

// EF DbContext
IQueryable<Person> personer = dbContext.Personer;   // Personer er en database tabel
var filtreredePersoner = personer.Where(p => p.Alder > 30);

som ender i noget ala:

select * from personer where alder > 30;

Lige nu skal du bare kende forskellen mellem IEnumerable og IQueryable, og i grundlæggende LINQ er IQueryable ikke så vigtig.

Avancerede metoder

LINQ tilbyder en rig syntaks, der kan håndtere komplekse dataforespørgsler. En dybere forståelse af denne syntaks åbner for mere avancerede forespørgsler.

Join

Join bruges i LINQ til at kombinere to sekvenser baseret på nøgleværdier og returnere flade par af matchende elementer.

Her er den grundlæggende syntaks:

var result = outerSequence.Join(
    innerSequence,
    outerKeySelector,
    innerKeySelector,
    resultSelector);
  • outerSequence: Den ydre sekvens, som vi vil sammenkæde.
  • innerSequence: Den indre sekvens, der skal sammenkædes med den ydre.
  • outerKeySelector: En funktion til at udtrække nøglen fra den ydre sekvens.
  • innerKeySelector: En funktion til at udtrække nøglen fra den indre sekvens.
  • resultSelector: En funktion, der definerer hvordan de sammenkædede objekter skal projekteres i resultatet.

Her er et eksempel:

var customers = new[]
{
    new { CustomerId = 1, Name = "John" },
    new { CustomerId = 2, Name = "Jane" }
};

var orders = new[]
{
    new { OrderId = 1, CustomerId = 1, Amount = 100 },
    new { OrderId = 2, CustomerId = 2, Amount = 150 },
    new { OrderId = 3, CustomerId = 1, Amount = 200 }
};

var customerOrders = customers.Join(
    orders,
    customer => customer.CustomerId,
    order => order.CustomerId,
    (customer, order) => new
    {
        CustomerName = customer.Name,
        OrderAmount = order.Amount
    });

foreach (var item in customerOrders)
{
    Console.WriteLine($"Customer: {item.CustomerName}, Order Amount: {item.OrderAmount}");
}

Dette eksempel kombinerer kunder og deres ordrer ved hjælp af CustomerId og viser resultaterne som flade par af kundernavne og ordrebeløb.

GroupJoin

GroupJoin bruges i LINQ til at kombinere to sekvenser baseret på nøgleværdier og returnere grupperede resultater. Den giver mulighed for at lave en gruppering af relaterede elementer fra to datakilder.

Her er den grundlæggende syntaks:

var result = outerSequence.GroupJoin(
    innerSequence,
    outerKeySelector,
    innerKeySelector,
    resultSelector);
  • outerSequence: Den ydre sekvens, som vi vil sammenkæde.
  • innerSequence: Den indre sekvens, der skal sammenkædes med den ydre.
  • outerKeySelector: En funktion til at udtrække nøglen fra den ydre sekvens.
  • innerKeySelector: En funktion til at udtrække nøglen fra den indre sekvens.
  • resultSelector: En funktion, der definerer hvordan de sammenkædede objekter skal projekteres i resultatet.

Her er et eksempel:

var customers = new[]
{
    new { CustomerId = 1, Name = "John" },
    new { CustomerId = 2, Name = "Jane" }
};

var orders = new[]
{
    new { OrderId = 1, CustomerId = 1, Amount = 100 },
    new { OrderId = 2, CustomerId = 2, Amount = 150 },
    new { OrderId = 3, CustomerId = 1, Amount = 200 }
};

var customerOrders = customers.GroupJoin(
    orders,
    customer => customer.CustomerId,
    order => order.CustomerId,
    (customer, ordrer) => new
    {
        CustomerName = customer.Name,
        Orders = ordrer
    });

foreach (var customer in customerOrders)
{
    Console.WriteLine($"Customer: {customer.CustomerName}");
    foreach (var order in customer.Orders)
    {
        Console.WriteLine($"  Order Amount: {order.Amount}");
    }
}

Dette eksempel kombinerer kunder og deres ordrer ved hjælp af CustomerId og viser resultaterne i en struktureret form.

SelectMany

SelectMany er en metode i LINQ (Language Integrated Query) i C#, der flader en samling af samlinger ud. Det er ofte brugt, når du har en sekvens af sekvenser, og du vil behandle det som en enkelt sekvens.

Her er et eksempel på, hvordan du kan bruge SelectMany:

List<List<int>> listOfLists = new List<List<int>>
{
    new List<int> { 1, 2, 3 },
    new List<int> { 4, 5, 6 },
    new List<int> { 7, 8, 9 }
};

IEnumerable<int> flattened = listOfLists.SelectMany(subList => subList);

foreach(int number in flattened)
{
    Console.WriteLine(number);
}

I dette eksempel har vi en liste af lister af integers. Vi bruger SelectMany til at flade listen af lister ud til en enkelt sekvens af integers. Outputtet vil være:

1
2
3
4
5
6
7
8
9

Her er et andet eksempel. Forestil dig, at vi har en Faktura klasse, som indeholder en liste af FakturaLinje objekter. Vi vil gerne hente en fladet liste af alle fakturalinjer fra en liste af fakturaer:

// Opretter en liste af fakturaer
var fakturaer = new List<Faktura>
{
    new Faktura
    {
        FakturaId = 1,
        FakturaLinjer = new List<FakturaLinje>
        {
            new FakturaLinje { Produkt = "Blyant", Antal = 10, PrisPrEnhed = 2 },
            new FakturaLinje { Produkt = "Kuglepen", Antal = 5, PrisPrEnhed = 5 }
        }
    },
    new Faktura
    {
        FakturaId = 2,
        FakturaLinjer = new List<FakturaLinje>
        {
            new FakturaLinje { Produkt = "Notesbog", Antal = 3, PrisPrEnhed = 15 },
            new FakturaLinje { Produkt = "Viskelæder", Antal = 7, PrisPrEnhed = 3 }
        }
    }
};

// Anvender SelectMany til at flade listen af fakturaer ud til en liste af fakturalinjer
var alleFakturaLinjer = fakturaer.SelectMany(f => f.FakturaLinjer);

foreach (var linje in alleFakturaLinjer)
{
    Console.WriteLine($"Produkt: {linje.Produkt}, Antal: {linje.Antal}, Pris pr. enhed: {linje.PrisPrEnhed}");
}

class Faktura
{
    public int FakturaId { get; set; }
    public List<FakturaLinje> FakturaLinjer { get; set; }
}

class FakturaLinje
{
    public string Produkt { get; set; }
    public int Antal { get; set; }
    public decimal PrisPrEnhed { get; set; }
}

I dette eksempel har vi to Faktura objekter, hver med sin egen liste af FakturaLinje objekter. Ved at anvende SelectMany, kombinerer vi disse lister af fakturalinjer til en enkelt liste, hvilket gør det lettere at arbejde med dem i en samlet kontekst. Det er en meget effektiv måde at arbejde med samlinger af samlinger på.

SelectMany er meget nyttigt, når du arbejder med komplekse datastrukturer, hvor du har brug for at flade en samling af samlinger ud til en enkelt sekvens.

Brugerdefinerede LINQ Udvidelser

At oprette en brugerdefineret LINQ-udvidelse er en kraftfuld måde at tilføje specifik funktionalitet til LINQ-forespørgsler, der muligvis ikke er dækket af de indbyggede metoder. En brugerdefineret LINQ-udvidelse implementeres som en extensionmetode.

Info

Læs om Extension metoder inden du fortsætter.

Eksempel: Even og Odd

At tilføje Even og Odd som brugerdefinerede LINQ-udvidelser kan være en god idé, da det giver en intuitiv og lettilgængelig måde at filtrere samlinger af tal baseret på, om de er lige eller ulige.

Først defineres udvidelsesmetoderne Even og Odd i en statisk klasse:

public static class LinqExtensions
{
    public static IEnumerable<int> Even(this IEnumerable<int> source)
    {
        return source.Where(n => n % 2 == 0);
    }

    public static IEnumerable<int> Odd(this IEnumerable<int> source)
    {
        return source.Where(n => n % 2 != 0);
    }
}

I disse metoder anvender vi Where-metoden fra LINQ. For Even-metoden tjekker vi, om et tal n er lige ved at se, om resten er 0, når n divideres med 2. For Odd-metoden gør vi det modsatte.

List<int> tal = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var ligeTal = tal.Even();
var uligeTal = tal.Odd();

Console.WriteLine("Lige tal:");
foreach (var n in ligeTal)
{
    Console.WriteLine(n);
}

Console.WriteLine("\nUlige tal:");
foreach (var n in uligeTal)
{
    Console.WriteLine(n);
}

Outputtet for denne kode vil være en liste af lige tal efterfulgt af en liste af ulige tal fra den oprindelige liste tal:

Lige tal:
2
4
6
8
10

Ulige tal:
1
3
5
7
9

Eksempel: TakeEvery

At tilføje en brugerdefineret LINQ-udvidelse, som TakeEvery, der tillader at tage hvert andet, tredje, fjerde element osv., kan være særligt nyttig, når du arbejder med store datasæt og kun er interesseret i en delmængde af dataene baseret på en bestemt intervalsekvens. Her er et eksempel på, hvordan du kan implementere en sådan udvidelse:

Metoden tilføjes til den samme LinqExtensions-klasse som Even og Odd:

public static IEnumerable<T> TakeEvery<T>(this IEnumerable<T> source, int interval)
{
    if (interval <= 0)
    {
        throw new ArgumentException("Interval skal være større end 0", nameof(interval));
    }

    int index = 0;
    foreach (var item in source)
    {
        if (index++ % interval == 0)
        {
            yield return item;
        }
    }
}

I denne metode bruger vi yield return til at returnere hvert interval‘te element fra den givne source samling. Hvis interval er sat til 2, vil metoden returnere hvert andet element, og hvis det er sat til 3, hvert tredje element, og så videre.

Her er et eksempel på, hvordan du kan bruge TakeEvery-metoden:

List<int> tal = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var hvertAndetTal = tal.TakeEvery(2);
var hvertTredjeTal = tal.TakeEvery(3);

Console.WriteLine("Hvert andet tal:");
foreach (var n in hvertAndetTal)
{
    Console.WriteLine(n);
}

Console.WriteLine("\nHvert tredje tal:");
foreach (var n in hvertTredjeTal)
{
    Console.WriteLine(n);
}

Outputtet for denne kode vil være en liste af hvert andet tal efterfulgt af en liste af hvert tredje tal fra den oprindelige liste tal.

Hvert andet tal:
1
3
5
7
9

Hvert tredje tal:
1
4
7
10

LINQ og performance

LINQ (Language Integrated Query) er en kraftfuld feature i C# og .NET, der tillader udviklere at skrive komplekse data queries direkte i C#. Men som med enhver anden teknologi, kan brugen af LINQ have potentielle performanceproblemer, hvis det ikke bruges korrekt. Her er nogle af de mest almindelige områder, hvor LINQ kan påvirke performance:

  1. Unødvendig gentagelse af queries: LINQ queries er “lazy” i det, de kun udføres, når resultatet er nødvendigt. Dette betyder, at hver gang du refererer til en LINQ query, bliver den udført igen. Hvis du bruger det samme resultat flere gange, skal du gemme resultatet i en variabel og genbruge den i stedet for at udføre den samme query flere gange. Du kan også bruge .ToList() eller .ToArray() for at tvinge udførelsen af en query med det samme.

  2. For tidlig udførelse af queries: På den anden side kan det at tvinge en query til at blive udført for tidligt (for eksempel ved at bruge .ToList() eller .ToArray()) også være et problem, hvis du kun har brug for en del af dataene. Hvis du kun har brug for det første element, der opfylder en betingelse, kan det være mere effektivt at bruge .FirstOrDefault() i stedet for at udføre hele queryen og derefter hente det første element.

  3. Brug af ineffektive operators: Nogle LINQ operators kan være meget ineffektive, hvis de ikke bruges korrekt. For eksempel, .Contains(), .Any(), .All(), .First(), .Last() kan alle potentielt gennemgå hele samlingen. Hvis du arbejder med store samlinger, skal du være opmærksom på, hvilke operators du bruger.

  4. Brug af LINQ i hot paths: LINQ er generelt langsommere end traditionelle loops, fordi det medfører ekstra overhead for at tilbyde sin fleksible syntaks og features. Hvis du har en del af din kode, der bliver kaldt meget ofte (en “hot path”), kan det være mere effektivt at bruge traditionelle loops i stedet for LINQ.

  5. Brug af LINQ med Entity Framework eller andre ORMs: Når du bruger LINQ med Entity Framework eller andre Object-Relational Mappers (ORMs), skal du være opmærksom på, at dine LINQ queries bliver oversat til SQL. Nogle LINQ queries kan resultere i meget ineffektiv SQL, hvilket kan medføre performanceproblemer. Det er altid en god ide at teste dine LINQ queries og overvåge den genererede SQL for at sikre, at den er effektiv.

Overordnet set, selvom LINQ kan være meget kraftfuldt og bekvemt, er det vigtigt at være opmærksom på, hvordan det kan påvirke performance. Som med enhver anden teknologi, er den bedste måde at sikre god performance at profilere din kode, identificere flaskehalse og optimere der, hvor det er nødvendigt.

Opgaver

Link til timer

LINQ mod filsystemet

Et godt eksempel på, at LINQ ikke kun er til lister og databaser, er filsystemet. DirectoryInfo har en metode kaldet EnumerateFiles, der returnerer IEnumerable<FileInfo> — og det betyder, at du kan bruge alle de LINQ-metoder, du allerede kender, direkte på filer og mapper.

var mappe = new DirectoryInfo(@"c:\temp");

// De 5 største filer
var størsteFiler = mappe
    .EnumerateFiles("*.*", SearchOption.AllDirectories)
    .OrderByDescending(f => f.Length)
    .Take(5)
    .Select(f => new { f.Name, KB = f.Length / 1024 });

foreach (var f in størsteFiler)
    Console.WriteLine($"{f.Name} ({f.KB} KB)");

// Filer grupperet på filtype
var prType = mappe
    .EnumerateFiles("*.*", SearchOption.AllDirectories)
    .GroupBy(f => f.Extension.ToLower())
    .Select(g => new { Type = g.Key == "" ? "(ingen)" : g.Key, Antal = g.Count() })
    .OrderByDescending(x => x.Antal);

foreach (var t in prType)
    Console.WriteLine($"{t.Type,-15} {t.Antal,4} filer");

Det virker fordi EnumerateFiles returnerer IEnumerable<FileInfo>, og FileInfo har egenskaber som Name, Length, Extension, LastWriteTime osv., som du kan filtrere og sortere på præcis som med andre objekter.

Tip

EnumerateFiles er at foretrække frem for GetFiles, fordi EnumerateFiles er lazy — den begynder at returnere resultater mens den stadig gennemgår mappen, frem for at indlæse alle filer i hukommelsen på én gang.

En simpel wrapper-klasse

Du kan indkapsle adgangen til filsystemet i en klasse, der eksponerer filer og mapper som IEnumerable. Det giver et pænt og ensartet API:

var kilde = new FilSystemKilde(@"c:\temp");

Console.WriteLine($"Filer i alt: {kilde.Filer.Count()}");
Console.WriteLine($"Undermapper: {kilde.Mapper.Count()}");

Console.WriteLine("Nyeste 3 filer:");
foreach (var f in kilde.Filer.OrderByDescending(f => f.LastWriteTime).Take(3))
    Console.WriteLine($"  {f.Name}  ({f.LastWriteTime:dd-MM-yyyy HH:mm})");

class FilSystemKilde
{
    private readonly DirectoryInfo _rod;

    public FilSystemKilde(string sti)
    {
        _rod = new DirectoryInfo(sti);
    }

    public IEnumerable<FileInfo> Filer =>
        _rod.EnumerateFiles("*.*", SearchOption.AllDirectories);

    public IEnumerable<DirectoryInfo> Mapper =>
        _rod.EnumerateDirectories("*", SearchOption.AllDirectories);
}

Klassen er meget simpel — den pakker blot DirectoryInfo ind og returnerer IEnumerable<FileInfo> og IEnumerable<DirectoryInfo>. Det er nok til at al din LINQ-kode kan arbejde direkte med filsystemet.

Hvad er en LINQ-provider?

Det her er faktisk kernen i, hvad en LINQ-provider er. Når du bruger LINQ mod en liste, er det IEnumerable der er provideren. Når du bruger LINQ mod en database via Entity Framework, er det en IQueryable-provider, der oversætter dine LINQ-udtryk til SQL.

Vores FilSystemKilde er en meget simpel provider baseret på IEnumerable — den henter alle data ind i hukommelsen og lader LINQ arbejde med dem der. En rigtig IQueryable-provider ville i stedet oversætte LINQ-udtrykket til noget filsystemet forstår, inden data hentes. Det er mere komplekst at implementere, men til mange formål er IEnumerable-tilgangen fuldt tilstrækkelig.

En mock IQueryable-provider mod GitHub

Filsystem-eksemplet var IEnumerable-baseret — alle data hentes ind i hukommelsen, og LINQ arbejder med dem der. Men hvad nu hvis du vil illustrere, at filteret kører på serveren — ligesom IQueryable gør mod en database?

GitHub’s søge-API er et godt eksempel: du sender dine filtre som query-parametre, og GitHub returnerer kun de matchende resultater. Du henter aldrig data du ikke har brug for.

Her er en simpel wrapper-klasse der lader dig bygge søgningen op med en flydende syntaks. Filtrene opsamles som parametre — HTTP-kaldet sker først, når du kalder FetchAsync():

using System.Text.Json.Nodes;

// Hent fra GitHub (returneret sorteret efter stjerner)
var resultater = await new GitHubRepoSøgning()
    .WithLanguage("csharp")
    .SortByStars()
    .Take(10)
    .FetchAsync();

// LINQ to objects: sortér alfabetisk i stedet
Console.WriteLine("Top 10 C# projekter, sorteret alfabetisk:");
foreach (var r in resultater.OrderBy(r => r.FullName))
    Console.WriteLine($"{r.FullName,-45} {r.Stars,7} ⭐");

// Ny søgning med topic-filter
var blazor = await new GitHubRepoSøgning()
    .WithLanguage("csharp")
    .WithTopic("blazor")
    .SortByStars()
    .Take(5)
    .FetchAsync();

Console.WriteLine("\nBlazer-projekter (sorteret alfabetisk):");
foreach (var r in blazor.OrderBy(r => r.FullName))
    Console.WriteLine($"{r.FullName,-45} {r.Stars,7} ⭐");

class GitHubRepoSøgning
{
    private string? _language;
    private string? _topic;
    private string  _sort  = "stars";
    private int     _count = 10;

    public GitHubRepoSøgning WithLanguage(string language) { _language = language; return this; }
    public GitHubRepoSøgning WithTopic(string topic)       { _topic    = topic;    return this; }
    public GitHubRepoSøgning SortByStars()                 { _sort     = "stars";  return this; }
    public GitHubRepoSøgning SortByActivity()              { _sort     = "updated"; return this; }
    public GitHubRepoSøgning Take(int count)               { _count    = count;    return this; }

    public async Task<List<GitHubRepo>> FetchAsync()
    {
        var q = new List<string>();
        if (_language != null) q.Add($"language:{_language}");
        if (_topic    != null) q.Add($"topic:{_topic}");
        if (q.Count == 0)      q.Add("stars:>1000");

        var queryString = string.Join("+", q.Select(Uri.EscapeDataString));
        var url = $"https://api.github.com/search/repositories" +
                  $"?q={queryString}&sort={_sort}&order=desc&per_page={_count}";

        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("User-Agent", "csharp-bog-eksempel");
        var json = await client.GetStringAsync(url);
        var items = JsonNode.Parse(json)!["items"]!.AsArray();

        return items.Select(item => new GitHubRepo
        {
            FullName    = item!["full_name"]!.GetValue<string>(),
            Stars       = item["stargazers_count"]!.GetValue<int>(),
            Description = item["description"]?.GetValue<string>() ?? ""
        }).ToList();
    }
}

class GitHubRepo
{
    public string FullName    { get; set; } = "";
    public int    Stars       { get; set; }
    public string Description { get; set; } = "";
}

Outputtet er (vil variere over tid):

Top 10 C# projekter, sorteret alfabetisk:
2dust/v2rayN                                   104799 ⭐
DevToys-app/DevToys                             31320 ⭐
dotnet/aspnetcore                               37903 ⭐
files-community/Files                           43309 ⭐
...

Blazer-projekter (sorteret alfabetisk):
abpframework/abp                                14247 ⭐
DevToys-app/DevToys                             31320 ⭐
MudBlazor/MudBlazor                             10386 ⭐
...

Læg mærke til at der er to lag af LINQ her: GitHub-API’et sorterer efter stjerner (server-side), og bagefter bruger vi OrderBy til at sortere alfabetisk i hukommelsen (LINQ to objects). Det illustrerer godt de to verdener: hvad der sker på serveren, og hvad der sker lokalt efterfølgende.

Forbindelsen til IQueryable

Når du kalder .WithLanguage("csharp").WithTopic("blazor"), gemmes filtrene blot som parametre — der sker intet endnu. Først ved await FetchAsync() bygges URL’en og HTTP-kaldet sendes. Det er den samme deferred execution-tankegang som IQueryable bygger på.

En rigtig IQueryable-provider gør det samme, men i stedet for at du manuelt angiver filtre via metoder, oversætter den dine LINQ-udtryk (.Where(...), .OrderBy(...)) automatisk til serverens forespørgselssprog — SQL i Entity Framework, REST-parametre i et API. Det kræver tolkning af expression trees, som er komplekst at implementere, men ideen er den samme.

Eksterne biblioteker

Der findes en del 3. parts biblioteker der udvider LINQ - se eksempelvis

Spørgsmål til AI

For at få mest muligt ud af AI-værktøjer som ChatGPT, er det vigtigt at stille klare og præcise spørgsmål (og skabe det rigtige kontekst - se her). Her er nogle spørgsmål til denne side:

Grundlæggende spørgsmål til AI

  • Hvad er forskellen mellem IEnumerable og IQueryable i C#?
  • Hvornår skal jeg bruge IEnumerable vs IQueryable?
  • Hvordan fungerer Join i LINQ og hvornår skal jeg bruge det?
  • Hvordan fungerer GroupJoin i LINQ og hvornår skal jeg bruge det?
  • Hvordan fungerer SelectMany i LINQ og hvornår skal jeg bruge det?
  • Hvordan opretter jeg brugerdefinerede LINQ-udvidelser i C#