Quando si implementano delle API con .net Core utilizzando Entity Framework, alcuni problemi di prestazioni non dipendono dalla logica ma come si utilizzano le chiamate ad Entity Framework. Di seguito alcune regole base da rispettare per evitare problemi di prestazioni.
1. Recupera solo i dati necessari (proiezioni)
❌ Errato:
var users = await _dbContext.Users.ToListAsync();
— recuperi TUTTE le colonne (nome, email, password hash, immagine profilo…).
✅ Corretto:
var users = await _dbContext.Users
.Select(u => new { u.Name, u.Email })
.ToListAsync();
Proiettando solo ciò che serve riduci carico, velocizzi serializzazione e ottimizzi performance. Anche Microsoft lo consiglia nelle linee guida su querying efficiente e proiezioni (Microsoft Learn).
2. Evita il problema N+1 con Include
❌ Errato:
var users = await _dbContext.Users.ToListAsync();
foreach(var u in users)
var orders = await _dbContext.Orders.Where(o => o.UserId == u.Id).ToListAsync();
— N+1 round‑trip al DB.
✅ Soluzione:
var users = await _dbContext.Users
.Include(u => u.Orders)
.ToListAsync();
Include obbliga EF a caricare relazioni in un’unica query. Usa sempre strumenti di profiling (SQL Profiler, logging EF) per identificare problemi (Microsoft Learn, Microsoft Learn).
3. Utilizza AsNoTracking()
per letture in sola lettura
Per query che non modificano entità:
var users = await _dbContext.Users
.AsNoTracking()
.ToListAsync();
Riduce l’overhead di change tracking, abbassando RAM e migliorando performance su grandi dataset. Microsoft consiglia l’uso in query read-only (Medium).
⚠️ Applica AsNoTracking()
una sola volta all’inizio della query; non è necessario ripeterlo per ogni Include (Stack Overflow).
4. SQL raw parametrici: evita SQL injection
❌ Errato:
.FromSqlRaw($"SELECT * FROM Users WHERE Name = '{inputName}'")
✅ Corretto:
.FromSqlInterpolated($"SELECT * FROM Users WHERE Name = {inputName}")
Oppure usa LINQ puro. Le query interpolated garantiscono parametrizzazione sicura (Microsoft Learn).
5. Index mancanti = query lente
Assicurati che le colonne usate nei WHERE
o join siano indicizzate:
CREATE NONCLUSTERED INDEX IX_Orders_UserId ON Orders(UserId);
Verifica il piano di esecuzione per trovare colonne non indicizzate e filtrare correttamente (Microsoft Learn).
6. Usa sempre metodi async in metodi async
❌ Errato:
var data = _dbContext.Users.ToList(); // Sincrono!
✅ Corretto:
var data = await _dbContext.Users.ToListAsync();
Mischiare sync e async può bloccare thread e costringere il thread pool in situazioni di carico elevato (Microsoft Learn).
7. Non tenere DbContext
in vita troppo a lungo
Il DbContext
è pensato per essere breve (per richiesta web). Registraralo con AddDbContext
(scoped), evita di conservarlo in servizi singleton — altrimenti rischi memory leak o bug di concorrenza (Microsoft Learn, Microsoft Learn).
8. Proiezioni manuali invece di AutoMapper in proiezioni grandi
AutoMapper è utile, ma può introdurre inefficienze se non controlli i campi caricati: meglio usare .Select(...)
manuale quando vuoi proiettare molti campi o avere controllo sui dati.
9. Usa transazioni con criterio
EF Core avvolge SaveChanges()
in una transazione per default. Non ne aggiungere all’occhio se non necessarie.
✅ Solo quando servono più operazioni atomiche:
using var txn = await context.Database.BeginTransactionAsync();
...
await txn.CommitAsync();
Evita transazioni annidate non necessarie.
10. Query tagging nelle produzioni
Usa .TagWith("MiTagQuery")
per marcare la query generata e facilitarne il debug nei log di SQL. Ottimo approccio per profilazione in produzione.
Qualche consiglio avanzato ulteriore dalla documentazione ufficiale
- Compiled queries: puoi pre‑compilare query frequenti con
EF.CompileAsyncQuery(...)
per ridurre il tempo di “compilazione” LINQ-to-SQL e migliorare latenza (Microsoft Learn). - DbContext pooling: attivando il pooling (
AddDbContextPool
) riduci overhead di creazione e distruzione dei contesti, utile se hai molte richieste contemporanee (Microsoft Learn). - Compiled models: su modelli EF Core grandi, puoi generare un modello compilato con
dotnet ef dbcontext optimize
per diminuire i tempi di startup (Microsoft Learn).
📋 Riepilogo tabellare
Problema comune | Soluzione consigliata |
---|---|
Recuperi troppi campi | Usa .Select(...) con proiezione |
Caricamenti relazioni inefficienti | Usa .Include(...) |
Tracking non necessario | Usa .AsNoTracking() |
Parametri SQL vulnerabili | Usa .FromSqlInterpolated(...) |
Query su colonna non indicizzata | Aggiungi index e analizza piano di esecuzione |
Mix sync/async | Utilizza sempre .ToListAsync() |
DbContext è troppo duraturo | Registralo scoped, evita singleton |
AutoMapper su proiezioni complesse | Meglio mappatura manuale con .Select(...) |
Transazioni non necessarie | Usa solo se serve atomicità multi-operazione |
Debug produzione | Tagga query con .TagWith(...) . |
Seguire queste linee guida può fare la differenza tra un’API lenta e una scalabile, mantenibile e veloce. Le fonti ufficiali Microsoft (Entity Framework Core – performance guide, efficient querying, compilazione, tracking) sono una base concreta su cui costruire API efficienti e affidabili (Microsoft Learn).