Query compilate e prestazioni
Aumenti le prestazioni delle query usando EF.CompileQuery, AsNoTracking e la proiezione con Select.
Query compilate e prestazioni è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.
Compilazione delle query EF Core
Ogni volta che EF Core esegue una query LINQ, deve analizzare l'albero delle espressioni, tradurlo in SQL e convalidarlo. Nei percorsi più utilizzati, questo sovraccarico si accumula. EF Core offre diversi strumenti per ridurlo al minimo.
AsNoTracking per le query di sola lettura
AsNoTracking() evita la registrazione nel change tracker. Per le query di sola lettura (risposte API, report), è il miglior intervento semplice in termini di prestazioni: in genere è da 2 a 5 volte più veloce e utilizza meno memoria.
// With tracking (default) — for entities you'll modify
var tracked = await _db.Products.FirstOrDefaultAsync(p => p.Id == 1);
// Without tracking — for read-only
var dto = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.Select(p => new ProductDto { Id = p.Id, Name = p.Name })
.ToListAsync();Query compilate
EF.CompileAsyncQuery precompila una query LINQ in un delegate. La traduzione avviene una volta all'avvio; le chiamate successive saltano completamente il passaggio di compilazione.
// Define once, reuse many times
private static readonly Func<AppDbContext, int, Task<Product?>> GetProductById =
EF.CompileAsyncQuery(
(AppDbContext db, int id) =>
db.Products.AsNoTracking().FirstOrDefault(p => p.Id == id));
// Usage
var product = await GetProductById(_db, 42);Proiezione con Select per le prestazioni
Esegua sempre una proiezione dei soli campi necessari. Selezionare un DTO invece di un'entità completa riduce le colonne recuperate, il payload di rete e la pressione sul GC.
// Load entire entity (over-fetch)
var all = await _db.Products.ToListAsync();
// Projection: only fetch needed columns
var names = await _db.Products
.AsNoTracking()
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// SQL: SELECT Id, Name, Price FROM ProductsHint degli indici e ottimizzazione delle query raw
EF Core rispetta gli indici del database configurati con HasIndex(). Gli indici composti, gli indici filtrati e le colonne incluse influiscono tutti sulle prestazioni del piano di esecuzione delle query.
// Configure compound index in OnModelCreating
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.CustomerId, o.Status })
.HasFilter("[Status] = 0") // filtered index
.IncludeProperties(o => o.PlacedAt);
// The query below will use this index efficiently
var pending = await _db.Orders
.AsNoTracking()
.Where(o => o.CustomerId == 5 && o.Status == OrderStatus.Pending)
.ToListAsync();Paginazione con Skip e Take
Esegua sempre la paginazione per gli insiemi di risultati di grandi dimensioni. Skip e Take vengono tradotti in SQL OFFSET/FETCH, evitando di caricare migliaia di righe in memoria.
int page = 2, pageSize = 25;
var products = await _db.Products
.AsNoTracking()
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// SQL: ... ORDER BY Name OFFSET 25 ROWS FETCH NEXT 25 ROWS ONLYContare in modo efficiente
Utilizzi CountAsync() o AnyAsync() invece di caricare i record solo per contarli. AnyAsync() è ancora più veloce di COUNT quando è sufficiente verificare l'esistenza di almeno un elemento.
// Total count for pagination
int totalCount = await _db.Products
.Where(p => p.IsActive)
.CountAsync();
// Existence check — faster than Count > 0
bool hasExpensive = await _db.Products
.AnyAsync(p => p.Price > 1000m);
// SQL: IF EXISTS (SELECT 1 FROM ...) SELECT 1Inserimenti bulk con AddRange
Utilizzi AddRange per aggiungere più entità contemporaneamente. EF Core raggruppa gli INSERT in un numero inferiore di round trip, controllato da MaxBatchSize nel provider.
var newProducts = Enumerable.Range(1, 1000)
.Select(i => new Product { Name = $"Product {i}", Price = i * 1.5m })
.ToList();
_db.Products.AddRange(newProducts);
await _db.SaveChangesAsync();
// Batched: typically 100-1000 rows per round-tripEF.Functions per le funzioni del database
EF.Functions espone funzioni specifiche del database (LIKE, parti di data, ricerca full-text) che EF Core traduce in SQL invece di valutarle in memoria.
// LIKE pattern search (SQL-side)
var results = await _db.Products
.Where(p => EF.Functions.Like(p.Name, "%keyboard%"))
.AsNoTracking()
.ToListAsync();
// Date difference (SQL-side)
var recentOrders = await _db.Orders
.Where(o => EF.Functions.DateDiffDay(o.PlacedAt, DateTime.UtcNow) <= 7)
.ToListAsync();Diagnosticare le query lente
Abiliti la registrazione delle query EF Core per visualizzare l'SQL generato. In fase di sviluppo, utilizzi EnableSensitiveDataLogging() per visualizzare i valori dei parametri.
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlServer(connStr)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging() // dev only!
.EnableDetailedErrors()); // dev only!Nella pratica: endpoint elenco ottimizzato
Un endpoint elenco pronto per la produzione combina AsNoTracking, proiezione, indici e paginazione per ottenere le massime prestazioni.
app.MapGet("/products", async (
AppDbContext db,
string? search,
int page = 1, int size = 20) =>
{
var query = db.Products
.AsNoTracking()
.Where(p => p.IsActive);
if (!string.IsNullOrEmpty(search))
query = query.Where(p => EF.Functions.Like(p.Name, $"%{search}%"));
var total = await query.CountAsync();
var items = await query
.OrderBy(p => p.Name)
.Skip((page - 1) * size).Take(size)
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
return Results.Ok(new { total, items });
});Verifica rapida
Qual è il principale vantaggio di EF.CompileAsyncQuery rispetto a una normale query LINQ?
Riepilogo: query compilate e prestazioni
Punti chiave:
AsNoTracking(): evita il change tracker per la sola lettura — il miglior intervento semplice in termini di velocitàEF.CompileAsyncQuery: precompila le query più utilizzate in delegate- Proiezione con
Select: recupera solo le colonne necessarie - Indici appropriati (
HasIndex) migliorano notevolmente i piani di esecuzione delle query - Utilizzi la paginazione con
Skip/Take; utilizziAnyAsyncper verificare l'esistenza - Abiliti la registrazione delle query in fase di sviluppo per individuare SQL lenti o imprevisti
Domande Frequenti
La lezione «Query compilate e prestazioni» è gratuita?
Sì — il testo completo di «Query compilate e prestazioni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.
Cosa imparerò in «Query compilate e prestazioni»?
Aumenti le prestazioni delle query usando EF.CompileQuery, AsNoTracking e la proiezione con Select. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare C# Academy?
Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Query compilate e prestazioni»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione C# Academy?
Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Caricamento eager, lazy ed esplicito
- SQL nativo, stored procedure e interpolazione
- Query compilate e prestazioni
- Filtri globali per le query ed entità possedute