Condividi tramite


Esecuzione di query

Dopo essere stata creata da un utente, una query LINQ viene convertita in una struttura ad albero dei comandi. Una struttura ad albero dei comandi è una rappresentazione di una query compatibile con Entity Framework. La struttura ad albero dei comandi viene quindi eseguita sull'origine dati. In fase di runtime della query, tutte le espressioni di query, ovvero tutti i componenti della query, vengono valutate, incluse le espressioni utilizzate nella materializzazione del risultato.

Il momento di esecuzione delle espressioni di query può variare. Le query LINQ vengono sempre eseguite quando la variabile di query viene scorsa e non quando viene creata. Questo processo è noto come esecuzione posticipata. È inoltre possibile forzare l'esecuzione immediata di una query. Questa operazione è utile per memorizzare nella cache i risultati della query e verrà descritta di seguito in questo argomento.

Quando viene eseguita una query LINQ to Entities, è possibile che alcune espressioni nella query vengano eseguite nel server e che alcune parti vengano eseguite localmente nel client. La valutazione sul lato client di un'espressione viene effettuata prima dell'esecuzione della query nel server. Se un'espressione viene valutata nel client, il risultato della valutazione sostituisce l'espressione nella query e la query viene quindi eseguita nel server. Poiché le query vengono eseguite sull'origine dati, la configurazione dell'origine dati prevale sul comportamento specificato nel client. La gestione dei valori Null e la precisione numerica dipendono ad esempio dalle impostazioni del server. Tutte le eccezioni generate durante l'esecuzione della query nel server vengono passate direttamente al client.

Esecuzione di query posticipata

In una query che restituisce una sequenza di valori, la variabile di query stessa non contiene mai i risultati della query ma viene utilizzata solo per l'archiviazione dei comandi della query. L'esecuzione della query viene posticipata fino a quando non viene eseguita un'iterazione della variabile di query in un ciclo foreach o For Each. In base a questo approccio, noto come esecuzione posticipata, la query viene eseguita qualche tempo dopo essere stata costruita. È quindi possibile eseguire una query il numero di volte desiderato. Tale caratteristica è utile, ad esempio, quando si dispone di un database che viene aggiornato da altre applicazioni. Nell'applicazione è possibile creare una query per recuperare le informazioni più recenti ed eseguire ripetutamente la query che restituisce ogni volta le informazioni aggiornate.

L'esecuzione posticipata consente di combinare più query o di estendere una query. Una query estesa viene modificata in modo da includere nuove operazioni. Le modifiche verranno quindi riflesse nell'eventuale esecuzione. Nell'esempio seguente la prima query restituisce tutti i prodotti. La seconda query estende la prima utilizzando Where per restituire tutti i prodotti di taglia "L":

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

Dopo aver eseguito una query, per tutte le query successive vengono utilizzati gli operatori LINQ in memoria. Se si scorre una variabile di query utilizzando un'istruzione foreach o For Each o chiamando uno degli operatori di conversione LINQ, la query viene eseguita immediatamente. Gli operatori di conversione includono ToList, ToArray, ToLookup e ToDictionary.

Esecuzione di query immediata

A differenza dell'esecuzione posticipata delle query che restituiscono una sequenza di valori, le query che restituiscono un valore singleton vengono eseguite immediatamente. Alcuni esempi di query singleton sono Average, Count, First e Max. Tali query vengono eseguite immediatamente perché la query deve restituire una sequenza per calcolare il risultato singleton. È anche possibile forzare l'esecuzione immediata. Questa operazione è utile quando si desidera memorizzare nella cache i risultati di una query. Per forzare l'esecuzione immediata di una query che non restituisce un valore singleton, è possibile chiamare il metodo ToList, ToDictionary o ToArray su una query o su una variabile di query. Nell'esempio seguente viene utilizzato il metodo ToArray per restituire immediatamente una matrice da una sequenza.

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

È anche possibile forzare l'esecuzione inserendo il ciclo foreach o For Each immediatamente dopo l'espressione di query, mentre chiamando ToList o ToArray si memorizzano nella cache tutti i dati in un singolo oggetto Collection.

Esecuzione nell'archivio

Poiché in genere le espressioni in LINQ to Entities vengono valutate nel server, non è previsto che il comportamento dell'espressione sia conforme alla semantica CLR (Common Language Runtime), ma piuttosto a quella dell'origine dati. Vi sono tuttavia eccezioni, ad esempio quando l'espressione viene eseguita nel client. Questo può provocare risultati imprevisti, ad esempio quando il server e il client si trovano in fusi orari diversi.

Alcune espressioni nella query possono venire eseguite nel client. In generale, è previsto che la maggior parte dell'esecuzione della query avvenga nel server. Oltre ai metodi eseguiti su elementi della query mappati all'origine dati, vi sono spesso espressioni nella query che possono essere eseguite localmente. L'esecuzione locale di un'espressione di query produce un valore che può essere utilizzato nell'esecuzione della query o nella costruzione del risultato.

Determinate operazioni vengono eseguite sempre nel client, ad esempio l'associazione di valori, le sottoespressioni, le sottoquery e la materializzazione di oggetti nei risultati della query. Di conseguenza questi elementi, ad esempio i valori dei parametri, non possono essere aggiornati durante l'esecuzione. I tipi anonimi possono essere costruiti inline nell'origine dati, ma questo comportamento non deve essere presupposto. Anche i raggruppamenti inline possono essere costruiti nell'origine dati, ma questo comportamento non deve essere presupposto in ogni istanza. In generale, è consigliabile non fare presupposizioni relativamente agli elementi che verranno costruiti nel server.

In questa sezione vengono descritti scenari in cui il codice viene eseguito localmente nel client. Per ulteriori informazioni sui tipi di espressioni eseguiti localmente, vedere Espressioni nelle query LINQ to Entities.

Valori letterali e parametri

Le variabili locali, ad esempio la variabile orderID nell'esempio seguente, vengono valutate nel client.

Dim orderID As Integer = 51987

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

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

Anche i parametri dei metodi vengono valutati nel client. Un esempio è costituito dal parametro orderID passato al metodo MethodParameterExample, come illustrato di seguito.

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

Esecuzione del cast di valori letterali nel client

Il cast da un valore null a un tipo CLR viene eseguito nel client:

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

Il cast a un tipo, ad esempio un oggetto Decimal che ammette i valori Null, viene eseguito nel client:

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

Costruttori per i valori letterali

I nuovi tipi CLR di cui è possibile eseguire il mapping al modello concettuale vengono eseguiti nel client:

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

Anche le nuove matrici vengono eseguite nel client.

Eccezioni nell'archivio

Qualsiasi errore che si verifica nell'archivio durante l'esecuzione della query viene passato al client e non viene mappato o gestito.

Configurazione dell'archivio

Quando la query viene eseguita nell'archivio, la configurazione dell'archivio sostituisce tutti i comportamenti client e la semantica dell'archivio viene espressa per tutte le operazioni e le espressioni. Ciò può determinare una differenza nel comportamento tra l'esecuzione nell'archivio e quella conforme a CLR per quanto riguarda aspetti come il confronto di valori Null, l'ordinamento di GUID, la precisione e l'accuratezza di operazioni che includono tipi di dati non precisi, ad esempio tipi a virgola mobile o DateTime, e le operazioni di stringa. È importante tenere presente questo aspetto quando si esaminano i risultati della query.

Di seguito sono ad esempio illustrate alcune differenze nel comportamento tra CLR e SQL Server:

  • In SQL Server i GUID vengono ordinati in modo diverso rispetto a CLR.

  • Possono esserci anche differenze nella precisione del risultato in caso di utilizzo del tipo Decimal in SQL Server. Queste differenze sono dovute ai requisiti fissi di precisione del tipo Decimal in SQL Server. La media dei valori Decimal 0,0, 0,0 e 1,0 è ad esempio 0,3333333333333333333333333333 nella memoria del client, ma 0,333333 nell'archivio, in base alla precisione predefinita per il tipo Decimal di SQL Server.

  • Anche alcune operazioni di confronto di stringhe vengono gestite in SQL Server in modo diverso rispetto a quanto avviene in CLR. Il comportamento del confronto di stringhe dipende dalle impostazioni delle regole di confronto nel server.

  • Le chiamate alle funzioni o ai metodi, quando incluse in una query LINQ to Entities, vengono mappate a funzioni canoniche in Entity Framework, che vengono convertite quindi in Transact-SQL ed eseguite nel database di SQL Server. In alcuni casi il comportamento delle funzioni mappate può differire dall'implementazione nelle librerie di classi di base. La chiamata ai metodi Contains, StartsWith e EndsWith con una stringa vuota come parametro restituisce ad esempio true quando eseguita in CLR, mentre restituisce false in caso di esecuzione in SQL Server. Anche il metodo EndsWith può restituire risultati diversi, in quanto due stringhe che differiscono solo per lo spazio vuoto finale vengono considerate uguali in SQL Server ma non in CLR. Questo comportamento è illustrato nell'esempio seguente:

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

}