Operatori di query complessi

LinQ (Language Integrated Query) contiene molti operatori complessi, che combinano più origini dati o eseguono un'elaborazione complessa. Non tutti gli operatori LINQ dispongono di traduzioni appropriate sul lato server. In alcuni casi, una query in un modulo viene convertita nel server, ma se scritta in un formato diverso non viene convertita anche se il risultato è lo stesso. Questa pagina descrive alcuni degli operatori complessi e le relative varianti supportate. Nelle versioni future è possibile riconoscere altri modelli e aggiungere le traduzioni corrispondenti. È anche importante tenere presente che il supporto della traduzione varia tra i provider. Una query specifica, che viene convertita in SqlServer, potrebbe non funzionare per i database SQLite.

Suggerimento

È possibile visualizzare l'esempio di questo articolo in GitHub.

Join.

L'operatore LINQ Join consente di connettere due origini dati in base al selettore di chiave per ogni origine, generando una tupla di valori quando la chiave corrisponde. Si traduce naturalmente in INNER JOIN in database relazionali. Mentre LINQ Join include selettori di chiave esterna e interna, il database richiede una singola condizione di join. Ef Core genera quindi una condizione di join confrontando il selettore di chiave esterna con il selettore della chiave interna per verificarne l'uguaglianza.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on photo.PersonPhotoId equals person.PhotoId
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON [p0].[PersonPhotoId] = [p].[PhotoId]

Inoltre, se i selettori di chiave sono tipi anonimi, EF Core genera una condizione di join per confrontare i componenti di uguaglianza.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
                equals new { Id = person.PhotoId, Caption = "SN" }
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON ([p0].[PersonPhotoId] = [p].[PhotoId] AND ([p0].[Caption] = N'SN'))

GroupJoin

L'operatore LINQ GroupJoin consente di connettere due origini dati simili a Join, ma crea un gruppo di valori interni per la corrispondenza degli elementi esterni. L'esecuzione di una query come nell'esempio seguente genera un risultato di Blog & IEnumerable<Post>. Poiché i database (in particolare i database relazionali) non hanno un modo per rappresentare una raccolta di oggetti lato client, GroupJoin non si traduce nel server in molti casi. Richiede di ottenere tutti i dati dal server per eseguire GroupJoin senza un selettore speciale (prima query riportata di seguito). Tuttavia, se il selettore limita i dati selezionati, il recupero di tutti i dati dal server può causare problemi di prestazioni (seconda query riportata di seguito). Ecco perché EF Core non converte GroupJoin.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, grouping };
var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, Posts = grouping.Where(p => p.Content.Contains("EF")).ToList() };

SelectMany

L'operatore LINQ SelectMany consente di enumerare su un selettore di raccolta per ogni elemento esterno e generare tuple di valori da ogni origine dati. In un certo senso, si tratta di un join, ma senza alcuna condizione, in modo che ogni elemento esterno sia connesso a un elemento dall'origine della raccolta. A seconda del modo in cui il selettore di raccolta è correlato all'origine dati esterna, SelectMany può tradursi in varie query sul lato server.

Il selettore di raccolta non fa riferimento all'esterno

Quando il selettore di raccolta non fa riferimento a nulla dall'origine esterna, il risultato è un prodotto cartesiano di entrambe le origini dati. Viene convertito CROSS JOIN in in database relazionali.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
CROSS JOIN [Posts] AS [p]

Riferimenti al selettore di raccolta esterni in una clausola where

Quando il selettore di raccolta ha una clausola where, che fa riferimento all'elemento esterno, EF Core lo converte in un join di database e usa il predicato come condizione di join. In genere questo caso si verifica quando si usa lo spostamento della raccolta sull'elemento esterno come selettore di raccolta. Se la raccolta è vuota per un elemento esterno, non verrà generato alcun risultato per tale elemento esterno. Tuttavia, se DefaultIfEmpty viene applicato al selettore di raccolta, l'elemento esterno verrà connesso con un valore predefinito dell'elemento interno. A causa di questa distinzione, questo tipo di query si traduce INNER JOIN in assenza di DefaultIfEmpty e LEFT JOIN quando DefaultIfEmpty viene applicato.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

Riferimenti al selettore di raccolta esterni in un caso non in cui

Quando il selettore di raccolta fa riferimento all'elemento esterno, che non si trova in una clausola where (come nel caso precedente), non si traduce in un join di database. Ecco perché è necessario valutare il selettore di raccolta per ogni elemento esterno. Si traduce in APPLY operazioni in molti database relazionali. Se la raccolta è vuota per un elemento esterno, non verrà generato alcun risultato per tale elemento esterno. Tuttavia, se DefaultIfEmpty viene applicato al selettore di raccolta, l'elemento esterno verrà connesso con un valore predefinito dell'elemento interno. A causa di questa distinzione, questo tipo di query si traduce CROSS APPLY in assenza di DefaultIfEmpty e OUTER APPLY quando DefaultIfEmpty viene applicato. Alcuni database come SQLite non supportano APPLY gli operatori, pertanto questo tipo di query potrebbe non essere tradotto.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
CROSS APPLY [Posts] AS [p]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
OUTER APPLY [Posts] AS [p]

GroupBy

Gli operatori LINQ GroupBy creano un risultato di tipo IGrouping<TKey, TElement> in cui TKey e TElement possono essere di qualsiasi tipo arbitrario. Inoltre, IGrouping implementa IEnumerable<TElement>, il che significa che è possibile comporre su di esso usando qualsiasi operatore LINQ dopo il raggruppamento. Poiché nessuna struttura di database può rappresentare un IGrouping, gli operatori GroupBy non hanno alcuna conversione nella maggior parte dei casi. Quando un operatore di aggregazione viene applicato a ogni gruppo, che restituisce un valore scalare, può essere convertito in SQL GROUP BY nei database relazionali. Anche SQL GROUP BY è restrittivo. È necessario raggruppare solo in base ai valori scalari. La proiezione può contenere solo colonne chiave di raggruppamento o qualsiasi aggregazione applicata su una colonna. EF Core identifica questo modello e lo converte nel server, come nell'esempio seguente:

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]

EF Core converte anche le query in cui un operatore di aggregazione sul raggruppamento viene visualizzato in un operatore LINQ Where o OrderBy (o altro ordinamento). Usa la HAVING clausola in SQL per la clausola where. La parte della query prima di applicare l'operatore GroupBy può essere qualsiasi query complessa, purché possa essere convertita nel server. Inoltre, dopo aver applicato gli operatori di aggregazione in una query di raggruppamento per rimuovere i raggruppamenti dall'origine risultante, è possibile crearne uno come qualsiasi altra query.

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            where g.Count() > 0
            orderby g.Key
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]

Gli operatori di aggregazione supportati da EF Core sono i seguenti:

.NET SQL
Average(x => x.Property) AVG(Property)
Count() COUNT(*)
LongCount() COUNT(*)
Max(x => x.Property) MAX(Property)
Min(x => x.Property) MIN(Property)
Sum(x => x.Property) SUM(Property)

È possibile supportare operatori di aggregazione aggiuntivi. Per altri mapping delle funzioni, vedere la documentazione del provider.

Anche se non esiste alcuna struttura di database per rappresentare un IGroupingoggetto , in alcuni casi, EF Core 7.0 e versioni successive possono creare i raggruppamenti dopo la restituzione dei risultati dal database. Questo è simile al funzionamento dell'operatore Include quando si includono raccolte correlate. La query LINQ seguente usa l'operatore GroupBy per raggruppare i risultati in base al valore della proprietà Price.

var query = context.Books.GroupBy(s => s.Price);
SELECT [b].[Price], [b].[Id], [b].[AuthorId]
FROM [Books] AS [b]
ORDER BY [b].[Price]

In questo caso, l'operatore GroupBy non viene convertito direttamente in una GROUP BY clausola in SQL, ma EF Core crea invece i raggruppamenti dopo che i risultati vengono restituiti dal server.

Left Join

Anche se Left Join non è un operatore LINQ, i database relazionali hanno il concetto di left join usato di frequente nelle query. Un modello specifico nelle query LINQ restituisce lo stesso risultato di un LEFT JOIN nel server. EF Core identifica tali modelli e genera l'equivalente LEFT JOIN sul lato server. Il modello prevede la creazione di un elemento GroupJoin tra entrambe le origini dati e quindi l'appiattimento del raggruppamento usando l'operatore SelectMany con DefaultIfEmpty nell'origine di raggruppamento in modo che corrisponda a Null quando l'elemento interno non ha un elemento correlato. Nell'esempio seguente viene illustrato l'aspetto del modello e l'aspetto generato.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            from p in grouping.DefaultIfEmpty()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

Il modello precedente crea una struttura complessa nell'albero delle espressioni. Per questo motivo, EF Core richiede di rendere flat i risultati del raggruppamento dell'operatore GroupJoin in un passaggio immediatamente successivo all'operatore. Anche se viene usato GroupJoin-DefaultIfEmpty-SelectMany ma in un modello diverso, non è possibile identificarlo come left join.