EF Core: interogări (Where, Include, AsNoTracking) în C# (.NET)

Ecosistem .NET: DI, EF Core și teste EF Core 1.0 (ExecuteUpdate: EF Core 7)

Interogările LINQ pe DbSet devin SQL: Where, Select, Include pentru relații, AsNoTracking pentru citire și ExecuteUpdate în masă.

Sintaxă C#
await db.Elevi.AsNoTracking().Where(e => e.Media >= 8).Include(e => e.Clasa).ToListAsync();

Ce face EF Core: interogări (Where, Include, AsNoTracking)

Pe un DbSet<T> scrii LINQ obișnuit, iar EF Core îl traduce în SQL abia când rezultatul e cerut: la ToList/ToListAsync, First, Count, Any sau la parcurgere. Select cu un tip anonim sau un DTO aduce din baza de date doar coloanele de care ai nevoie.

Relațiile nu se încarcă automat: Include(c => c.Elevi) aduce și elevii fiecărei clase, în aceeași interogare. AsNoTracking() spune contextului să nu urmărească obiectele, ceea ce e mai rapid pentru date doar de afișat.

ExecuteUpdateAsync și ExecuteDeleteAsync (EF Core 7) trimit direct un UPDATE/DELETE fără să încarce rândurile în memorie. Variantele Async ale metodelor sunt recomandate în aplicațiile web.

Exemple

Exemplul 1: Filtrare, proiecție și Include

using Microsoft.EntityFrameworkCore;

using var db = new ScoalaContext();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();

var c9a = new Clasa { Nume = "9A" };
var c9b = new Clasa { Nume = "9B" };
db.Elevi.AddRange(
    new Elev { Nume = "Ana", Media = 9, Clasa = c9a },
    new Elev { Nume = "Mihai", Media = 7, Clasa = c9a },
    new Elev { Nume = "Ioana", Media = 10, Clasa = c9b });
db.SaveChanges();

var buni = db.Elevi.AsNoTracking()
    .Where(e => e.Media >= 8)
    .OrderByDescending(e => e.Media)
    .Select(e => new { e.Nume, Clasa = e.Clasa.Nume })
    .ToList();
foreach (var e in buni)
    Console.WriteLine($"{e.Nume} ({e.Clasa})");

foreach (var c in db.Clase.Include(k => k.Elevi).OrderBy(k => k.Nume))
    Console.WriteLine($"{c.Nume}: elevi {c.Elevi.Count}");

public class Clasa
{
    public int Id { get; set; }
    public string Nume { get; set; } = "";
    public List<Elev> Elevi { get; set; } = [];
}

public class Elev
{
    public int Id { get; set; }
    public string Nume { get; set; } = "";
    public int Media { get; set; }
    public int ClasaId { get; set; }
    public Clasa Clasa { get; set; } = null!;
}

public class ScoalaContext : DbContext
{
    public DbSet<Elev> Elevi => Set<Elev>();
    public DbSet<Clasa> Clase => Set<Clasa>();

    protected override void OnConfiguring(DbContextOptionsBuilder options) =>
        options.UseSqlite("Data Source=scoala.db");
}

Rezultat

Ioana (9B)
Ana (9A)
9A: elevi 2
9B: elevi 1

Exemplul 2: Metode async și actualizare în masă

Continuă exemplul 1, cu aceleași date. ExecuteUpdateAsync mărește media lui Mihai direct în baza de date.

int actualizati = await db.Elevi
    .Where(e => e.Media < 8)
    .ExecuteUpdateAsync(s => s.SetProperty(e => e.Media, e => e.Media + 1));
Console.WriteLine($"Actualizați: {actualizati}");

Console.WriteLine(await db.Elevi.CountAsync(e => e.Clasa.Nume == "9A"));
Console.WriteLine(await db.Elevi.AnyAsync(e => e.Nume == "Radu"));
Console.WriteLine(await db.Elevi.MinAsync(e => e.Media));

var primul = await db.Elevi.OrderByDescending(e => e.Media).FirstAsync();
Console.WriteLine(primul.Nume);

Rezultat

Actualizați: 1
2
False
8
Ioana

Greșeli frecvente

  • Accesarea c.Elevi fără Include: colecția e goală (sau, cu lazy loading activat, fiecare acces face o interogare separată, problema N+1).
  • ToList() pus prea devreme (db.Elevi.ToList().Where(...)): toată tabela este adusă în memorie și filtrată acolo.
  • Apeluri de metode C# proprii în Where nu se pot traduce în SQL și aruncă InvalidOperationException la rulare.

Comenzi înrudite

  • Entity Framework Core: DbContext și DbSet: DbContext este legătura cu baza de date în EF Core: DbSet<T> reprezintă un tabel, iar SaveChanges salvează adăugările, modificările și ștergerile.
  • EF Core: migrări (dotnet ef migrations): Migrările țin schema bazei de date în pas cu modelul C#: migrations add generează codul schimbării, database update o aplică.
  • Skip / Take (paginare): Take ia primele n elemente, Skip le sare; împreună fac paginare. TakeWhile/SkipWhile se opresc la prima condiție falsă.