Dela via


Frågekörning

När en LINQ-fråga har skapats av en användare konverteras den till ett kommandoträd. Ett kommandoträd är en representation av en fråga som är kompatibel med Entity Framework. Kommandoträdet körs sedan mot datakällan. Vid frågekörningen utvärderas alla frågeuttryck (dvs. alla komponenter i frågan), inklusive de uttryck som används i resultatmaterialisering.

Vid vilken tidpunkt frågeuttryck körs kan variera. LINQ-frågor körs alltid när frågevariabeln itereras över, inte när frågevariabeln skapas. Detta kallas uppskjuten körning. Du kan också tvinga en fråga att köras omedelbart, vilket är användbart för cachelagring av frågeresultat. Detta beskrivs senare i det här avsnittet.

När en LINQ till entitetsfråga körs kan vissa uttryck i frågan köras på servern och vissa delar kan köras lokalt på klienten. Utvärdering på klientsidan av ett uttryck sker innan frågan körs på servern. Om ett uttryck utvärderas på klienten ersätts resultatet av utvärderingen med uttrycket i frågan och frågan körs sedan på servern. Eftersom frågor körs på datakällan åsidosätter datakällans konfiguration det beteende som anges i klienten. Till exempel är null-värdehantering och numerisk precision beroende av serverinställningarna. Eventuella undantag som utlöses under frågekörningen på servern skickas direkt upp till klienten.

Dricks

En praktisk sammanfattning av frågeoperatorer i tabellformat, som gör att du snabbt kan identifiera en operators körningsbeteende, finns i Klassificering av standardfrågeoperatorer efter körningssätt (C#).

Uppskjuten frågekörning

I en fråga som returnerar en sekvens med värden innehåller själva frågevariabeln aldrig frågeresultatet och lagrar bara frågekommandona. Körningen av frågan skjuts upp tills frågevariabeln itereras över i en foreach eller For Each -loop. Det här kallas för uppskjuten körning, dvs. frågekörning inträffar en tid efter att frågan har konstruerats. Det innebär att du kan köra en fråga så ofta du vill. Detta är användbart när du till exempel har en databas som uppdateras av andra program. I ditt program kan du skapa en fråga för att hämta den senaste informationen och köra frågan upprepade gånger och returnera den uppdaterade informationen varje gång.

Med uppskjuten körning kan flera frågor kombineras eller en fråga utökas. När en fråga utökas ändras den så att den inkluderar de nya åtgärderna, och den slutliga körningen återspeglar ändringarna. I följande exempel returnerar den första frågan alla produkter. Den andra frågan utökar den första med hjälp Where av för att returnera alla produkter med storleken "L":

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<Product> productsQuery =
        from p in context.Products
        select p;

    IQueryable<Product> largeProducts = productsQuery.Where(p => p.Size == "L");

    Console.WriteLine("Products of size 'L':");
    foreach (var product in largeProducts)
    {
        Console.WriteLine(product.Name);
    }
}
Using context As New AdventureWorksEntities()
    Dim productsQuery = _
        From p In context.Products _
        Select p

    Dim largeProducts = _
        productsQuery.Where(Function(p) p.Size = "L")

    Console.WriteLine("Products of size 'L':")
    For Each product In largeProducts
        Console.WriteLine(product.Name)
    Next
End Using

När en fråga har körts kommer alla efterföljande frågor att använda de minnesinterna LINQ-operatorerna. Om du itererar över frågevariabeln med hjälp av en foreach -instruktion eller For Each genom att anropa någon av LINQ-konverteringsoperatorerna kommer det att leda till omedelbar körning. Dessa konverteringsoperatorer innehåller följande: ToList, ToArray, ToLookupoch ToDictionary.

Omedelbar frågekörning

Till skillnad från den uppskjutna körningen av frågor som skapar en sekvens med värden körs frågor som returnerar ett singleton-värde omedelbart. Några exempel på singleton-frågor är Average, Count, Firstoch Max. Dessa körs omedelbart eftersom frågan måste generera en sekvens för att beräkna singleton-resultatet. Du kan också framtvinga omedelbar körning. Detta är användbart när du vill cachelagra resultatet av en fråga. Om du vill framtvinga omedelbar körning av en fråga som inte genererar ett singleton-värde kan du anropa ToList metoden, ToDictionary metoden eller ToArray metoden på en fråga eller frågevariabel. I följande exempel används ToArray metoden för att omedelbart utvärdera en sekvens till en matris.

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    ObjectSet<Product> products = context.Products;

    Product[] prodArray = (
        from product in products
        orderby product.ListPrice descending
        select product).ToArray();

    Console.WriteLine("Every price from highest to lowest:");
    foreach (Product product in prodArray)
    {
        Console.WriteLine(product.ListPrice);
    }
}
Using context As New AdventureWorksEntities
    Dim products As ObjectSet(Of Product) = context.Products

    Dim prodArray As Product() = ( _
        From product In products _
        Order By product.ListPrice Descending _
        Select product).ToArray()

    Console.WriteLine("The list price from highest to lowest:")
    For Each prod As Product In prodArray
        Console.WriteLine(prod.ListPrice)
    Next
End Using

Du kan också framtvinga körning genom att placera loopen foreach eller For Each omedelbart efter frågeuttrycket, men genom att anropa ToList eller ToArray cachelagrat alla data i ett enda samlingsobjekt.

Lagringskörning

I allmänhet utvärderas uttryck i LINQ till entiteter på servern, och uttryckets beteende bör inte förväntas följa CLR-semantik (Common Language Runtime), utan datakällans. Det finns dock undantag till detta, till exempel när uttrycket körs på klienten. Detta kan orsaka oväntade resultat, till exempel när servern och klienten befinner sig i olika tidszoner.

Vissa uttryck i frågan kan köras på klienten. I allmänhet förväntas den flesta frågekörningar ske på servern. Förutom metoder som körs mot frågeelement som mappas till datakällan finns det ofta uttryck i frågan som kan köras lokalt. Lokal körning av ett frågeuttryck ger ett värde som kan användas i frågekörningen eller resultatkonstruktionen.

Vissa åtgärder körs alltid på klienten, till exempel bindning av värden, underuttryck, underfrågor från stängningar och materialisering av objekt i frågeresultat. Nettoeffekten av detta är att dessa element (till exempel parametervärden) inte kan uppdateras under körningen. Anonyma typer kan konstrueras infogade i datakällan, men bör inte antas göra det. Infogade grupper kan också konstrueras i datakällan, men detta bör inte antas i varje instans. I allmänhet är det bäst att inte göra några antaganden om vad som konstrueras på servern.

I det här avsnittet beskrivs scenarier där kod körs lokalt på klienten. Mer information om vilka typer av uttryck som körs lokalt finns i Uttryck i LINQ till entitetsfrågor.

Literaler och parametrar

Lokala variabler, till exempel variabeln orderID i följande exempel, utvärderas på klienten.

int orderID = 51987;

IQueryable<SalesOrderHeader> salesInfo =
    from s in context.SalesOrderHeaders
    where s.SalesOrderID == orderID
    select s;
Dim orderID As Integer = 51987

Dim salesInfo = _
    From s In context.SalesOrderHeaders _
    Where s.SalesOrderID = orderID _
    Select s

Metodparametrar utvärderas också på klienten. Parametern orderID som skickas MethodParameterExample till metoden nedan är ett exempel.

public static void MethodParameterExample(int orderID)
{
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {

        IQueryable<SalesOrderHeader> salesInfo =
            from s in context.SalesOrderHeaders
            where s.SalesOrderID == orderID
            select s;

        foreach (SalesOrderHeader sale in salesInfo)
        {
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
        }
    }
}
Function MethodParameterExample(ByVal orderID As Integer)
    Using context As New AdventureWorksEntities()

        Dim salesInfo = _
            From s In context.SalesOrderHeaders _
            Where s.SalesOrderID = orderID _
            Select s

        Console.WriteLine("Sales order info:")
        For Each sale As SalesOrderHeader In salesInfo
            Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue)
        Next
    End Using

End Function

Gjuta literaler på klienten

Gjutning från null till en CLR-typ körs på klienten:

IQueryable<Contact> query =
    from c in context.Contacts
    where c.EmailAddress == (string)null
    select c;
Dim query = _
    From c In context.Contacts _
    Where c.EmailAddress = CType(Nothing, String) _
    Select c

Gjutning till en typ, till exempel en nullbar Decimal, körs på klienten:

var weight = (decimal?)23.77;
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;
Dim weight = CType(23.77, Decimal?)
Dim query = _
    From product In context.Products _
    Where product.Weight = weight _
    Select product

Konstruktorer för literaler

Nya CLR-typer som kan mappas till konceptuella modelltyper körs på klienten:

var weight = new decimal(23.77);
IQueryable<Product> query =
    from product in context.Products
    where product.Weight == weight
    select product;
Dim weight = New Decimal(23.77)
Dim query = _
    From product In context.Products _
    Where product.Weight = weight _
    Select product

Nya matriser körs också på klienten.

Store-undantag

Alla butiksfel som påträffas under frågekörningen skickas till klienten och mappas eller hanteras inte.

Lagringskonfiguration

När frågan körs i arkivet åsidosätter lagringskonfigurationen alla klientbeteenden och butikssemantik uttrycks för alla åtgärder och uttryck. Detta kan resultera i en skillnad i beteendet mellan CLR och lagringskörning inom områden som nulljämförelser, GUID-ordning, precision och noggrannhet för åtgärder som involverar icke-exakta datatyper (till exempel flyttaltyper eller DateTime) och strängåtgärder. Det är viktigt att tänka på detta när du undersöker frågeresultat.

Följande är till exempel några skillnader i beteendet mellan CLR och SQL Server:

  • SQL Server beställer GUID:er på ett annat sätt än CLR.

  • Det kan också finnas skillnader i resultatprecision när du hanterar decimaltypen på SQL Server. Detta beror på de fasta precisionskraven för SQL Server-decimaltypen. Till exempel är medelvärdet av Decimal värdena 0.0, 0.0 och 1.0 0.3333333333333333333333333333 i minnet på klienten, men 0,333333 i arkivet (baserat på standardprecisionen för SQL Server-decimaltypen).

  • Vissa strängjämförelseåtgärder hanteras också annorlunda i SQL Server än i CLR. Beteendet för strängjämförelse beror på sorteringsinställningarna på servern.

  • Funktions- eller metodanrop, när de ingår i en LINQ-till-entitetsfråga, mappas till kanoniska funktioner i Entity Framework, som sedan översätts till Transact-SQL och körs på SQL Server-databasen. Det finns fall då beteendet som dessa mappade funktioner uppvisar kan skilja sig från implementeringen i basklassbiblioteken. Om du till exempel anropar Containsmetoderna , StartsWithoch EndsWith med en tom sträng som parameter returneras true när de körs i CLR, men returneras false när de körs i SQL Server. Metoden EndsWith kan också returnera olika resultat eftersom SQL Server anser att två strängar är lika om de bara skiljer sig åt i avslutande blanksteg, medan CLR anser att de inte är lika med. Detta illustreras av följande exempel:

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    IQueryable<string> query = from p in context.Products
                               where p.Name == "Reflector"
                               select p.Name;

    IEnumerable<bool> q = query.Select(c => c.EndsWith("Reflector "));

    Console.WriteLine("LINQ to Entities returns: " + q.First());
    Console.WriteLine("CLR returns: " + "Reflector".EndsWith("Reflector "));
}
Using context As New AdventureWorksEntities()

    Dim query = _
        From p In context.Products _
        Where p.Name = "Reflector" _
        Select p.Name

    Dim q = _
        query.Select(Function(c) c.EndsWith("Reflector "))

    Console.WriteLine("LINQ to Entities returns: " & q.First())
    Console.WriteLine("CLR returns: " & "Reflector".EndsWith("Reflector "))
End Using