Cialtr.One

Entity Framework: come far schifo con le computed properties

Scritto il — 12 ago 2025
#csharp #entity-framework #linq #dotnet #performance
A rig of something techy

Photo by panumas nikhomkhai on Pexels

Sono passati almeno cent’anni dall’ultimo articolo che ho pubblicato in questo ameno luogo. E’ cambiato tutto, il mondo è a rotoli.

Il rotolamento del mondo odierno è certamente dovuto alla noia della vita senza i miei articoli, la mia prosa affilata e i miei ragionamenti profondi. Solo qualità, ormai perduta.

Poniamo fine a questo scempio.

Pochi giorni fa, mentre svolgevo la mia attività preferita, lavorare, ho scoperto che usando la direttiva Select di Linq, all’interno di una query Entity Framework, e selezionando una computed property dell’entità, la query, inevitabilmente, fa schifo.

In poche parole, il framework non riesce a determinare quali campi sono necessari per la computed property e quindi selezionerà tutti i campi dell’entità. Badabim Badabum!

Per esempio, il codice seguente che, tra le altre cose, esegue una select sul campo FullName dell’entità User:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var missingSubmission = await db.ItpReportApprovals
    .Where(r => r.Month == month)
    .Where(r => r.StatusCode == (int) ApprovalStatus.Missing || r.StatusCode == (int) ApprovalStatus.Saved)
    .Include(x => x.StrategicBusinessUnit)
    .Include(x => x.Manager)
    .Select(x => new {
        ManagerName = x.Manager.FullName,
        ManagerEmail = x.Manager.Email,
        SbuName = x.StrategicBusinessUnit.Name
    })
    .ToListAsync();

produce la query SQL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
SELECT
  u.id,
  u.account_manager_id,
  u.address,
  u.birthplace,
  u.citizenship,
  u.date_of_birth,
  u.email,
  u.fiscal_code,
  u.gender,
  u.gis_code,
  u.has_legally_protected_status,
  u.holidays,
  u.home_address,
  u.name,
  u.password_hash,
  u.permission,
  u.personal_email,
  u.phone,
  u.role,
  u.status,
  u.surname,
  s.name
FROM
  prod.itp_report_approvals AS i
  INNER JOIN prod.users AS u ON i.manager_id = u.id
  INNER JOIN prod.strategic_business_units AS s ON i.strategic_business_unit_id = s.id
WHERE
  i.month = @ __month_0
  AND i.status IN (4, 3);

questo perchè il campo FullName dell’entità User è definito come:

1
public string FullName  => $"{Name} {Surname}"

Entity Framework non riesce a convertire in maniera ottimizzata la query e quindi seleziona tutti i campi dell’entità. Andando contro quello che Microsoft stessa suggerisce per le query, ovvero ridurre allo stretto necessario i campi ritornati.

Provando a riscrivere il codice C# come segue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var missingSubmission = await db.ItpReportApprovals
    .Where(r => r.Month == month)
    .Where(r => r.StatusCode == (int) ApprovalStatus.Missing || r.StatusCode == (int) ApprovalStatus.Saved)
    .Include(x => x.StrategicBusinessUnit)
    .Include(x => x.Manager)
    .Select(x => new {
        ManagerName = x.Manager.Name,
        ManagerSurname = x.Manager.Surname,
        ManagerEmail = x.Manager.Email,
        SbuName = x.StrategicBusinessUnit.Name
    })
    .ToListAsync();

e quindi facendo leva unicamente sui campi Name e Surname che sono mappati su colonne del database:

1
2
[Column("name")] public string Name { get; set; }
[Column("surname")] public string Surname { get; set; }

La risultante query SQL sarà:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
SELECT
  u.name    AS "ManagerName",
  u.surname AS "ManagerSurname",
  u.email   AS "ManagerEmail",
  s.name    AS "SbuName"
FROM
  prod.itp_report_approvals AS i
  INNER JOIN prod.users AS u ON i.manager_id = u.id
  INNER JOIN prod.strategic_business_units AS s ON i.strategic_business_unit_id = s.id
WHERE
  i.month = @ __month_0
  AND i.status IN (4, 3)

Molto meglio! Come vedete il framework ha individuato correttamente i campi su cui eseguire la select.

A noi non rimane quindi che comporre Name e Surname in separata sede.

Risultato interessante che è stato permesso dal fatto che, da qualche tempo, ho messo mano e sistemato i log del progetto dal quale ho estratto il codice di cui sopra. Ora i log sono colorati (assolutamente fondamentale) e riportano, in ambiente di sviluppo locale, le query che Entity Framework esegue.

Uno spasso.

Saluti.