Creazione del modello dei dati
La creazione di un modello dei dati1 è un’operazione molto importante perché definisce la struttura dei dati all’interno del database che andremo ad utilizzare. EF Core è un ORM, ossia un Object Relational Mapper: è un framework che permette di mappare oggetti su tabelle di un database relazionale. I Relational DBMS (RDBMS) saranno oggetto di studio dettagliato l’anno prossimo, ma per il momento si può immaginare un database relazionale come un insieme di file che memorizzano tabelle collegate tra di loro, mediante il meccanismo delle chiavi esterne come, ad esempio, le fatture sono collegate ai clienti, mediante il ClienteId all’interno della fattura.
Per semplicità2 si potrà assumere che ogni istanza di una classe sia mappata in una riga di una tabella che corrisponde alla classe:
Classe → Tabella
Oggetto → Riga di una tabella.
Nell’esempio precedente di GestioneFattureClienti
il modello creato con EF Core ha prodotto un database con le tabelle Fatture
e Clienti
. Aprendo il file di database FattureClienti.db
con un programma come SQLite browser si può vedere la struttura del database:
Per creare una tabella nel database si parte dalla definizione del modello come classe. Ad esempio, nel caso dei clienti siamo partiti dalla definizione della classe:
1public class Cliente
2{
3 public int ClienteId { get; set; }
4 public string RagioneSociale { get; set; } = null!;
5 public string PartitaIVA { get; set; } = null!;
6 public string? Citta { get; set; }
7 public string? Via { get; set; }
8 public string? Civico { get; set; }
9 public string? CAP { get; set; }
10 public List<Fattura> Fatture { get; } = [];
11
12 public override string ToString()
13 {
14 return $"{{{nameof(ClienteId)} = {ClienteId}, " +
15 $"{nameof(RagioneSociale)} = {RagioneSociale}, " +
16 $"{nameof(PartitaIVA)} = {PartitaIVA}, " +
17 $"{nameof(Citta)} = {Citta}, " +
18 $"{nameof(Via)} = {Via}, " +
19 $"{nameof(Civico)} = {Civico}, " +
20 $"{nameof(CAP)} = {CAP}}}";
21 }
22}
Si noti che EF Core utilizza alcune convenzioni e impostazioni di default (che possono essere modificate a patto di conoscere le caratteristiche di EF Core). Ad esempio, per creare una chiave primaria, ossia un campo che permette di identificare univocamente un’istanza di un oggetto all’interno di una tabella (ad esempio una riga all’interno della tabella dei Clienti
) bisogna utilizzare la convenzione NomeClasseId, oppure solamente Id, oppure ID. Ad esempio, il campo ClienteId
è chiave primaria (ad auto incremento) nella tabella che memorizzerà gli oggetti di tipo Cliente
.
Quando bisogna creare i collegamenti tra le tabelle del database occorre indicare dei legami tra gli oggetti delle classi che rappresentano le righe delle tabelle. Ad esempio, per esprimere il fatto che un cliente può avere molte fatture (una associazione 1 a molti) si è inserito all’interno della classe Cliente
la property:
1 public List<Fattura> Fatture { get; } = [];
Questa property verrà usata da EF Core per capire il tipo di collegamento da fare tra la tabella dei Clienti
e la tabella delle Fatture
.
Analogamente la classe che descrive le fatture è del tipo:
1public class Fattura
2{
3 public int FatturaId { get; set; }
4 public DateTime Data { get; set; }
5 public decimal Importo { get; set; }
6 public int ClienteId { get; set; }
7 public Cliente Cliente { get; set; } = null!;
8
9 public override string ToString()
10 {
11 return $"{{{nameof(FatturaId)} = {FatturaId}, " +
12 $"{nameof(Data)} = {Data.ToShortDateString()}, " +
13 $"{nameof(Importo)} = {Importo}, " +
14 $"{nameof(ClienteId)} = {ClienteId}}}";
15 }
16}
Nella classe Fattura
si notano alcuni elementi importantissimi:
La chiave primaria FatturaId
: è un campo di tipo intero che permette di individuare univocamente una fattura. Si può impostare da codice C# il valore da inserire nel database, ma solitamente è più comodo affidarsi alla proprietà di auto incremento che il database attribuisce alla chiave primaria, come si può vedere dallo schema della tabella che è stata creata da SQLite quando è stata fatta la migrazione.
La chiave esterna (foreign key) ClienteId
: è un campo che non è chiave primaria nella tabella in cui compare, ma lo è nella tabella riferita. In questo caso il campo ClienteId
all’interno della classe Fattura
permette di individuare univocamente il cliente intestatario della fattura. EF Core ha bisogno anche del campo Cliente
di tipo Cliente
nella classe Fattura
per creare la chiave esterna nella tabella delle Fatture
che punta alla chiave primaria della tabella dei Clienti
. Il campo Cliente
è usato da EF Core per definire il tipo di mapping tra la tabella delle Fatture
e la tabella dei Clienti
.
EF Core uses a metadata model to describe how the application’s entity types are mapped to the underlying database1. This model is built using a set of conventions - heuristics that look for common patterns. The model can then be customized using mapping attributes (also known as data annotations) and/or calls to the ModelBuilder methods (also known as fluent API) in OnModelCreating, both of which will override the configuration performed by conventions.
Per la configurazione del modello dei dati con EF Core si deve fare riferimento alla documentazione ufficiale relativa al modeling, da studiare nella parte generale e nella discussione dei casi generali di mapping: