Una funzionalità molto comune nelle applicazioni è quella di dover recuperare una lista di oggetti dati i loro id. Per esempio, potremmo dover creare un metodo che accetti una lista di id e restituisca le persone corrispondenti a quegli id. Il modo più semplice e veloce per eseguire questa operazione è usare il metodo LINQ Contains come mostrato nell'esempio.
People[] GetPeopleByIds(int[] ids){ return _ctx.People.Where(x => ids.Contains(x.Id)).ToArray(); }
Questa query produce un codice SQL simile a questo.
SELECT * FROM People WHERE Id IN (1,2,4,7,10,5)
Sebbene perfettamente funzionante, EF Core scrive gli id direttamente nel codice SQL. Questo significa che se il metodo viene chiamato spesso con parametri sempre diversi, Sql Server genera un query plan per ogni query, perchè la stringa SQL cambia sempre. Il risultato è che la cache dei plan di Sql Server è piena di query praticamente identiche dove quello che cambia sono solo gli id e questo va a scapito delle performance della cache in quanto ci sono troppe entry e alcune potrebbero rubare il posto ad altre più importanti.
Per evitare di mal utilizzare la cache dei plan, possiamo fare in modo di riscrivere la query in modo da evitare l'utilizzo dei dati cablati. Per fare questo possiamo creare una stringa che contiene gli id separati da virgola e passarla a una funzione di Sql Server che la trasforma in una tabella che poi mettiamo in join con quella in cui dobbiamo cercare i dati.
Il primo passo consiste nel creare la funzione che altro non è che una Table-Valued Function. Il risultato della funzione è una tabella con un campo di nome value di tipo int.
ALTER Function [dbo].[Int_Split](@string varchar(max)) Returns @Result Table (value int) As Begin declare @len int, @loc int = 1 While @loc <= len(@string) Begin Set @len = CHARINDEX(',', @string, @loc) - @loc If @Len < 0 Set @Len = len(@string) Insert Into @Result Values (SUBSTRING(@string,@loc,@len)) Set @loc = @loc + @len + 1 End Return End
Una volta creata la funzione, dobbiamo mapparne il risultato con una classe C# come se fosse una tabella.
public class IntSplitResult { public int Value { get; set; } }
Ora possiamo mappare la classe IntSplitResult come view (specificando che non ha una chiave) e la funzione SQL con una funzione C# utilizzabile nelle nostre query LINQ.
public partial class PeopleContext { ... partial void OnModelCreatingPartial(ModelBuilder modelBuilder) { modelBuilder.Entity<StringSplitResult>().HasNoKey(); } [DbFunction(IsBuiltIn = false, Name = "INT_SPLIT")] private IQueryable<IntSplitResult> IntSplit(string source) => FromExpression(() => IntSplit(source)); public IQueryable<int> AsSplit(IEnumerable<int> source) => IntSplit(string.Join(",", source.Select(x => Convert.ToString(x)))).Select(s => s.Value); }
Il metodo AsSplit prende in input la lista di id e la trasforma in una stringa di numeri separati da virgola per poi passarla alla funzione IntSplit che agisce come proxy verso la funzione in Sql Server. A questo punto possiamo scrivere la nostra query che sfrutta il metodo AsSplit.
People[] GetPeopleByIds(int[] ids){ return _ctx.People.Where(x => db.AsSplit(ids).Contains(x.Id)).ToArray(); }
Il codice SQL generato dalla query è il seguente (semplificato).
DECLARE @__source_1 nvarchar(4000) = N'1,2,3,4,5'; SELECT * FROM People AS p WHERE EXISTS ( SELECT 1 FROM Int_Split(@__source_1) AS [s] WHERE (s.Value = p.Id))
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Visualizzare le change sul plan di Terraform tramite le GitHub Actions
Supporto ai tipi DateOnly e TimeOnly in Entity Framework Core
Proteggere le risorse Azure con private link e private endpoints
Utilizzare Model as a Service su Microsoft Azure
Migliorare la sicurezza dei prompt con Azure AI Studio
Persistere la ChatHistory di Semantic Kernel in ASP.NET Core Web API per GPT
Sviluppare un'interfaccia utente in React con Tailwind CSS e Preline UI
Utilizzare EF.Constant per evitare la parametrizzazione di query SQL
Usare i servizi di Azure OpenAI e ChatGPT in ASP.NET Core con Semantic Kernel
.NET Conference Italia 2024
Filtering sulle colonne in una QuickGrid di Blazor
Assegnare un valore di default a un parametro di una lambda in C#
I più letti di oggi
- Creare un adorner personalizzato per le trading cards di PivotViewer in Silverlight 5.0
- Copiare uno Stream con il .NET Framework 4.0
- Supporto alla validazione client-side in una data annotation su ASP.NET MVC 3
- Disabilitare automaticamente un workflow di GitHub (parte 2)
- Dependency injection con Minimal API di ASP.NET Core
- Migliorare l'organizzazione delle risorse con Azure Policy
- Sfruttare una CDN con i bundle di ASP.NET
- Creare applicazioni in real-time con ASP.NET SignalR
- Memorizzare posizione e dimensioni della finestra di una applicazione OOB Silverlight 4.0
- Web Camp - HTML5 per il web di oggi