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.