Esercitazioni guidate sulla creazione di database e sull'esecuzione di query da programma C#, mediante LINQ
In questa sezione verranno mostrati alcuni esercizi completamente svolti relativamente alla creazione di un database mediante EF Core e all’esecuzione di query con il linguaggio LINQ, nella variante Fluent Method.
Creare un progetto per un’applicazione Console .Net Core
, chiamata Romanzi
che utilizzi il Framework EF Core per implementare il database (Romanzi.db
) con le seguenti classi:
Autore(
AutoreId
, Nome, Cognome, Nazionalità )
Romanzo(
RomanzoId
, Titolo, AutoreId*, AnnoPubblicazione )
Personaggio(
PersonaggioId
, Nome, RomanzoId*, Sesso, Ruolo )
Dove l’attributo sottolineato rappresenta la Primary Key
, mentre l’attributo con l’asterisco rappresenta una Foreign Key
.
Assumere che:
Nome
, Cognome
, Nazionalità
nella la classe Autore
siano di tipo string
Titolo
in Romanzo
sia di tipo string
;AnnoPubblicazione
in Romanzo
sia di tipo int
Nome
, Sesso
, Ruolo
in Personaggio
siano di tipo string
Nota N1: creare un progetto Console denominato Romanzi
da Visual Studio Code
Nota N2: installare le librerie per l’utilizzo di EF Core con SQLite:
1dotnet add package Microsoft.EntityFrameworkCore.Design
2dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Creare la cartella Model
con dentro le classi del modello dei dati
Creare la cartella Data
con al suo interno la classe RomanziContext
Nota N3: la classe RomanziContext
discende da DbContext
ed espone una property per ciascuna tabella del database, come esemplificato di seguito:
1 public DbSet<NomeClasse> NomeTabella { get; set; }
Nota N4: la classe RomanziContext
potrebbe, se richiesto, implementare i metodi:
1protected override void OnConfiguring(DbContextOptionsBuilder options);
2protected override void OnModelCreating(ModelBuilder modelBuilder);
Effettuare la migration del database e la creazione fisica del file di database di SQLite:
1 dotnet ef migrations add InitialCreate
2 dotnet ef database update
Creare un metodo static void PopulateDB()
nella classe Program
che inserisce:
Almeno 5 autori di nazionalità compresa tra “Americana”, “Belga”, “Inglese”.
Almeno 10 romanzi degli autori precedentemente inseriti
Almeno 5 personaggi presenti nei romanzi precedentemente inseriti.
Q1: creare un metodo che prende in input la nazionalità e stampa gli autori che hanno la nazionalità specificata
Q2: creare un metodo che prende in input il nome e il cognome di un autore e stampa tutti i romanzi di quell’autore
Q3: creare un metodo che prende in input la nazionalità e stampa quanti romanzi di quella nazionalità sono presenti nel database
Q4: creare un metodo che per ogni nazionalità stampa quanti romanzi di autori di quella nazionalità sono presenti nel database
Q5: creare un metodo che stampa il nome dei personaggi presenti in romanzi di autori di una data nazionalità
Seguendo lo stesso procedimento visto negli esempi precedenti si creino i file relativi alle classi Romanzo
, Autore
, Personaggio
nella cartella Model
:
File Romanzo.cs
:
1namespace Romanzi.Model;
2public class Romanzo
3{
4 public int RomanzoId { get; set; }
5 public string Titolo { get; set; } = null!;
6 public int AutoreId { get; set; }
7 public Autore Autore { get; set; } = null!;
8 public int? AnnoPubblicazione { get; set; }
9 public List<Personaggio> Personaggi { get; set; } = null!;
10
11}
File Autore.cs
:
1namespace Romanzi.Model;
2public class Autore
3{
4 public int AutoreId { get; set; }
5 public string Nome { get; set; } = null!;
6 public string Cognome { get; set; } = null!;
7 public string? Nazionalità { get; set; }
8 public List<Romanzo> Romanzi { get; set; } = null!;
9
10}
11
File Personaggio.cs
:
1namespace Romanzi.Model;
2public class Personaggio
3{
4 public int PersonaggioId { get; set; }
5 public string Nome { get; set; } = null!;
6 public int RomanzoId { get; set; }
7 public Romanzo Romanzo { get; set; } = null!;
8 public string? Sesso { get; set; }
9 public string? Ruolo { get; set; }
10
11}
Si installino i pacchetti necessari per l’utilizzo di EF Core con SQLite:
1 dotnet add package Microsoft.EntityFrameworkCore.Design
2 dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Si crei la cartella Data
con dentro il file RomanziContext.cs
, relativo alla definizione della classe RomanziContext
:
1using Microsoft.EntityFrameworkCore;
2using Romanzi.Model;
3
4namespace Romanzi.Data;
5
6public class RomanziContext : DbContext
7{
8 public DbSet<Personaggio> Personaggi { get; set; } = null!;
9 public DbSet<Autore> Autori { get; set; } = null!;
10 public DbSet<Romanzo> Romanzi { get; set; } = null!;
11 public string DbPath { get; }
12
13 public RomanziContext()
14 {
15 var appDir = AppContext.BaseDirectory;
16 var path = Path.Combine(appDir, "../../../Romanzi.db");
17 DbPath = path;
18 }
19 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
20 {
21 optionsBuilder.UseSqlite($"Data Source = {DbPath}");
22 }
23}
24
Dalla PCM di Visual Studio si eseguano i comandi per la generazione della migrazione e la creazione del database.
1 dotnet ef migrations add InitialCreate
2 dotnet ef database update
La struttura del progetto dovrebbe essere simile a quella riportata nella figura seguente:
Si scriva nel file Program.cs
il codice relativo a:
File Program.cs
:
1using Microsoft.EntityFrameworkCore;
2using Microsoft.EntityFrameworkCore.Infrastructure;
3using Microsoft.EntityFrameworkCore.Storage;
4using Romanzi.Data;
5using Romanzi.Model;
6
7InitTest();
8WriteLineWithColor("\nEsecuzione di Q1: " +
9 "\ncreare un metodo che prende in input la nazionalità e stampa gli autori che hanno la nazionalità specificata" +
10 "\nCaso della nazionalità Americana\n", ConsoleColor.Cyan);
11Q1("Americana");
12
13WriteLineWithColor("\nEsecuzione di Q2: " +
14 "\ncreare un metodo che prende in input il nome e il cognome di un autore e stampa tutti i romanzi di quell’autore" +
15 "\nCaso di Ernest Hemingway\n",ConsoleColor.Cyan);
16Q2("Ernest", "Hemingway");
17
18WriteLineWithColor("\nEsecuzione di Q3: " +
19 "\ncreare un metodo che prende in input la nazionalità e stampa quanti romanzi di quella nazionalità sono presenti nel database" +
20 "\nCaso della nazionalità Americana\n", ConsoleColor.Cyan);
21Q3("Americana");
22
23WriteLineWithColor("\nEsecuzione di Q4: " +
24 "\ncreare un metodo che per ogni nazionalità stampa quanti romanzi di autori di quella nazionalità sono presenti nel database\n", ConsoleColor.Cyan);
25Q4();
26WriteLineWithColor("\nEsecuzione di Q5: " +
27 "\ncreare un metodo che stampa il nome dei personaggi presenti in romanzi di autori di una data nazionalità" +
28 "\nCaso della nazionalità Inglese\n", ConsoleColor.Cyan);
29Q5("Inglese");
30
31//Q1: creare un metodo che prende in input la nazionalità e
32//stampa gli autori che hanno la nazionalità specificata
33static void Q1(string nazionalità)
34{
35 using var db = new RomanziContext();
36 Console.WriteLine($"Artisti di nazionalità {nazionalità}");
37 db.Autori
38 .Where(a => a.Nazionalità != null
39 && a.Nazionalità.Equals(nazionalità))
40 .ToList()
41 .ForEach(a => Console.WriteLine($"{a.Nome} {a.Cognome}"));
42
43}
44
45//Q2: creare un metodo che prende in input il nome e il cognome di un autore
46//e stampa tutti i romanzi di quell’autore
47static void Q2(string nome, string cognome)
48{
49 using var db = new RomanziContext();
50 //primo modo - uso di inner join
51 Console.WriteLine("primo modo - uso di Join");
52 db.Autori
53 .Where(a => a.Nome.Equals(nome)
54 && a.Cognome.Equals(cognome))//filtriamo per nome e cognome
55 .Join(db.Romanzi,//Join con romanzi
56 a => a.AutoreId,
57 r => r.AutoreId,
58 (a, r) => new { a.Nome, a.Cognome, r.Titolo, r.AnnoPubblicazione })//prendiamo le colonne che interessano
59 .ToList()//restituiamo al client il risultato
60 .ForEach(t => Console.WriteLine(t));//processiamo il risultato
61
62 //secondo metodo - uso di navigation property a partire da Romanzi
63 Console.WriteLine("secondo modo - uso di navigation property a partire da Romanzi");
64 db.Romanzi
65 .Where(r => r.Autore.Nome.Equals(nome)
66 && r.Autore.Cognome.Equals(cognome))
67 .ToList()//restituiamo al client il risultato
68 .ForEach(t => Console.WriteLine($"\t{t.Titolo} {t.AnnoPubblicazione}"));//processiamo il risultato
69
70 //terzo modo - uso di navigation property
71 Console.WriteLine("terzo modo - uso di navigation property a partire da Autori - più involuto");
72 db.Autori
73 .Where(a => a.Nome.Equals(nome)
74 && a.Cognome.Equals(cognome))//filtriamo per nome e cognome
75 .Select(a => new { a, RomanziDiAutore = a.Romanzi })//usiamo la navigation property a.Romanzi
76 .ToList()//restituiamo al client il risultato
77 .ForEach(t =>
78 { //processiamo il risultato
79 Console.WriteLine($"Autore = {t.a.Nome} {t.a.Cognome}");
80 if (t.RomanziDiAutore != null)
81 {
82 foreach (var libro in t.RomanziDiAutore)
83 {
84 Console.WriteLine($"\t{libro.Titolo} {libro.AnnoPubblicazione}");
85 }
86 }
87 });
88
89 //quarto modo - uso di navigation property e di Include
90 //👇👇👇
91 //ATTENZIONE! Le collection property e le navigation property non sono automaticamente caricate nell'app, quando si esegue una query.
92 //https://learn.microsoft.com/en-us/ef/core/querying/related-data/
93 //https://learn.microsoft.com/en-us/ef/core/querying/related-data/eager
94 //👆👆👆
95 //In questo esempio se togliessimo l'include e commentassimo i due modi precedenti di eseguire la query
96 //vedremmo che il risultato non includerebbe i Romanzi
97 Console.WriteLine("quarto modo - uso di navigation property e di Include");
98 db.Autori
99 .Where(a => a.Nome.Equals(nome)
100 && a.Cognome.Equals(cognome))//filtriamo per nome e cognome
101 .Include(a => a.Romanzi)//chiediamo al provider del database di caricare i romanzi tramite la navigation property
102 .ToList()//restituiamo al client il risultato
103 .ForEach(a =>
104 { //processiamo il risultato
105 Console.WriteLine($"Autore = {a.Nome} {a.Cognome}");
106 if (a.Romanzi != null)
107 {
108 foreach (var libro in a.Romanzi)
109 {
110 Console.WriteLine($"\t{libro.Titolo} {libro.AnnoPubblicazione}");
111 }
112 }
113 });
114}
115
116//Q3: creare un metodo che prende in input la nazionalità e stampa quanti romanzi di quella nazionalità
117//sono presenti nel database
118static void Q3(string nazionalità)
119{
120 using var db = new RomanziContext();
121
122 //primo modo - uso di join
123 var numeroRomanzi = db.Autori
124 .Where(a => a.Nazionalità != null
125 && a.Nazionalità.Equals(nazionalità))//filtriamo per nome e cognome
126 .Join(db.Romanzi,//Join con romanzi
127 a => a.AutoreId,
128 r => r.AutoreId,
129 (a, r) => new { r.RomanzoId })
130 .Count();
131 Console.WriteLine($"I romanzi di nazionalità {nazionalità} sono: {numeroRomanzi}");
132
133 //secondo modo - uso di navigation property
134 var numeroRomanzi2 = db.Romanzi
135 .Where(r => r.Autore.Nazionalità != null
136 && r.Autore.Nazionalità.Equals(nazionalità))
137 .Count();
138 Console.WriteLine("Secondo metodo");
139 Console.WriteLine($"I romanzi di nazionalità {nazionalità} sono: {numeroRomanzi2}");
140}
141//Q4: creare un metodo che per ogni nazionalità stampa quanti romanzi di autori di quella
142//nazionalità sono presenti nel database
143static void Q4()
144{
145 //primo modo - facciamo una join seguita da un raggruppamento
146 //In questo esempio è molto importante effettuare la Select dopo la GroupBy, altrimenti si ottiene un errore:
147 //la Select serve a completare l'esecuzione della query all'interno del database con il calcolo di eventuale
148 //funzione di gruppo. Solo successivamente, con il ToList si restituisce il risultato della GroupBy all'applicazione
149 Console.WriteLine("Primo metodo - uso di Join e di Group By");
150 using var db = new RomanziContext();
151 db.Autori
152 .Join(db.Romanzi,
153 a => a.AutoreId,
154 r => r.AutoreId,
155 (a, r) => new { Nazionalita = a.Nazionalità, r.RomanzoId })
156 .GroupBy(r => r.Nazionalita)//effettuiamo il raggruppamento
157 .Select(g => new { Nazionalità = g.Key, NumeroRomanzi = g.Count() })//calcoliamo la funzione di gruppo - Count in questo caso
158 .ToList() //restituiamo il risultato all'app
159 .ForEach(t => Console.WriteLine($"Nazionalità = {t.Nazionalità}; numero romanzi = {t.NumeroRomanzi}"));//processiamo il risultato
160
161 //secondo modo - uso di navigation properties
162 Console.WriteLine("\nSecondo metodo - uso di GroupBy con navigation properties");
163 db.Romanzi
164 .GroupBy(r => r.Autore.Nazionalità)//effettuo il raggruppamento
165 .Select(g => new { Nazionalità = g.Key, NumeroRomanzi = g.Count() })//calcoliamo la funzione di gruppo - Count in questo caso
166 .ToList()//restituiamo il risultato all'app
167 .ForEach(t => Console.WriteLine($"Nazionalità = {t.Nazionalità}; numero romanzi = {t.NumeroRomanzi}"));//processiamo il risultato
168}
169//Q5: creare un metodo che stampa il nome dei personaggi presenti in romanzi di autori di una data nazionalità
170static void Q5(string nazionalità)
171{
172 using var db = new RomanziContext();
173 //primo modo - uso di join di join
174 Console.WriteLine("Primo modo - uso di join");
175 db.Autori
176 .Where(a => a.Nazionalità != null
177 && a.Nazionalità.Equals(nazionalità))
178 .Join(db.Romanzi,
179 a => a.AutoreId,
180 r => r.AutoreId,
181 (a, r) => new { r.RomanzoId })
182 .Join(db.Personaggi,
183 r => r.RomanzoId,
184 p => p.RomanzoId,
185 (r, p) => p)
186 .ToList()
187 .ForEach(p => Console.WriteLine($"{p.Nome} {p.Ruolo} {p.Sesso}"));
188
189 //secondo modo - uso di navigation property
190 Console.WriteLine("\nSecondo modo - uso di navigation property");
191 db.Personaggi
192 .Where(p => p.Romanzo.Autore.Nazionalità != null
193 && p.Romanzo.Autore.Nazionalità.Equals(nazionalità))
194 .ToList()
195 .ForEach(p => Console.WriteLine($"{p.Nome} {p.Ruolo} {p.Sesso}"));
196
197}
198
199
200
201static void InitTest()
202{
203 RomanziContext db = new();
204 //verifichiamo se il database esista già
205 //https://medium.com/@Usurer/ef-core-check-if-db-exists-feafe6e36f4e
206 //https://stackoverflow.com/questions/33911316/entity-framework-core-how-to-check-if-database-exists
207 if (db.Database.GetService<IRelationalDatabaseCreator>().Exists())
208 {
209 WriteLineWithColor("Il database esiste già, vuoi ricrearlo da capo? Tutti i valori precedentemente inseriti verranno persi. [Si, No]", ConsoleColor.Red);
210 bool dbErase = Console.ReadLine()?.StartsWith("si", StringComparison.CurrentCultureIgnoreCase) ?? false;
211 if (dbErase)
212 {
213 //cancelliamo il database se esiste
214 db.Database.EnsureDeleted();
215 //ricreiamo il database a partire dal model (senza dati --> tabelle vuote)
216 db.Database.EnsureCreated();
217 //inseriamo i dati nelle tabelle
218 PopulateDb(db);
219 Console.WriteLine("Database ricreato correttamente");
220 }
221 }
222 else //il database non esiste
223 {
224 //ricreiamo il database a partire dal model (senza dati --> tabelle vuote)
225 db.Database.EnsureCreated();
226 //popoliamo il database
227 PopulateDb(db);
228 Console.WriteLine("Database creato correttamente");
229 }
230
231 static void PopulateDb(RomanziContext db)
232 {
233 //Almeno 5 autori di nazionalità compresa tra "Americana", "Belga", "Inglese".
234 List<Autore> autori =
235 [
236 new (){AutoreId=1, Nome="Ernest",Cognome="Hemingway", Nazionalità="Americana"},//AutoreId=1
237 new (){AutoreId=2,Nome="Philip",Cognome="Roth", Nazionalità="Americana"},//AutoreId=2
238 new (){AutoreId=3,Nome="Thomas",Cognome="Owen", Nazionalità="Belga"},//AutoreId=3
239 new (){AutoreId=4,Nome="William",Cognome="Shakespeare", Nazionalità="Inglese"},//AutoreId=4
240 new (){AutoreId=5,Nome="Charles",Cognome="Dickens", Nazionalità="Inglese"},//AutoreId=5
241 ];
242
243 autori.ForEach(a => db.Add(a));
244 db.SaveChanges();
245 //Almeno 10 romanzi degli autori precedentemente inseriti
246 List<Romanzo> romanzi =
247 [
248 new (){RomanzoId=1, Titolo="For Whom the Bell Tolls", AnnoPubblicazione=1940, AutoreId=1},//RomanzoId=1
249 new (){RomanzoId=2,Titolo="The Old Man and the Sea", AnnoPubblicazione=1952, AutoreId=1},
250 new (){RomanzoId=3,Titolo="A Farewell to Arms",AnnoPubblicazione=1929, AutoreId=1},
251 new (){RomanzoId=4,Titolo="Letting Go", AnnoPubblicazione=1962, AutoreId=2},
252 new (){RomanzoId=5,Titolo="When She Was Good", AnnoPubblicazione=1967, AutoreId=2},
253 new (){RomanzoId=6,Titolo="Destination Inconnue", AnnoPubblicazione=1942, AutoreId=3},
254 new (){RomanzoId=7,Titolo="Les Fruits de l'orage", AnnoPubblicazione=1984, AutoreId=3},
255 new (){RomanzoId=8,Titolo="Giulio Cesare", AnnoPubblicazione=1599, AutoreId=4},
256 new (){RomanzoId=9,Titolo="Otello", AnnoPubblicazione=1604, AutoreId=4},
257 new (){RomanzoId=10,Titolo="David Copperfield", AnnoPubblicazione=1849, AutoreId=5},
258 ];
259 romanzi.ForEach(r => db.Add(r));
260 db.SaveChanges();
261 //Almeno 5 personaggi presenti nei romanzi precedentemente inseriti
262 List<Personaggio> personaggi =
263 [
264 new (){PersonaggioId=1, Nome="Desdemona", Ruolo="Protagonista", Sesso="Femmina", RomanzoId=9},//PersonaggioId=1
265 new (){PersonaggioId=2,Nome="Jago", Ruolo="Protagonista", Sesso="Maschio", RomanzoId=9},
266 new (){PersonaggioId=3,Nome="Robert", Ruolo="Protagonista", Sesso="Maschio", RomanzoId=1},
267 new (){PersonaggioId=4,Nome="Cesare", Ruolo="Protagonista", Sesso="Maschio", RomanzoId=8},
268 new (){PersonaggioId=5,Nome="David", Ruolo="Protagonista", Sesso="Maschio", RomanzoId=10}
269 ];
270 personaggi.ForEach(p => db.Add(p));
271 db.SaveChanges();
272 }
273
274}
275
276//stampa a console con il colore di foreground selezionato e successivamente ripristina il colore precedente
277static void WriteLineWithColor(string text, ConsoleColor consoleColor)
278{
279 ConsoleColor previousColor = Console.ForegroundColor;
280 Console.ForegroundColor = consoleColor;
281 Console.WriteLine(text);
282 Console.ForegroundColor = previousColor;
283}
284
Creare un progetto per un’applicazione Console .Net Core
, chiamata DbUtilizziPC
che utilizzi il Framework EF Core per implementare il database (DbUtilizziPC.db
) con le seguenti classi:
Classe(
Id
, Nome, Aula )
Studente(
Id
, Nome, Cognome, ClasseId* )
Utilizza(
StudenteId*
,
ComputerId*
,
DataOraInizioUtilizzo
, DataOraFineUtilizzo )
Computer(
Id
, Modello, Collocazione )
Dove l’attributo sottolineato rappresenta la Primary Key
, mentre l’attributo con l’asterisco rappresenta una Foreign Key
.
Assumere che:
Id
siano di tipo intero ad auto-incrementoNome
, Aula
nell’entità Classe
siano di tipo string
Nome
in Cognome
nell’entità Studente
siano di tipo string
;DataOraInizioUtilizzo
e DataOraFineUtilizzo
nell’entità Utilizza
siano di tipo DateTime
Modello
e Collocazione
nell’entità Computer
siano di tipo string
Nota N1: creare un progetto Console denominato DbUtilizziPC
da Visual Studio
Nota N2: installare le librerie per l’utilizzo di EF Core con SQLite (dalla Packet Manager Console di Visual Studio):
1dotnet add package Microsoft.EntityFrameworkCore.Design
2dotnet add package Microsoft.EntityFrameworkCore.Sqlite
Creare la cartella Model
con dentro le classi del modello dei dati
Creare la cartella Data
con al suo interno la classe UtilizziPCContext
Nota N3: la classe UtilizziPCContext
discende da DbContext
ed espone una property per ciascuna tabella del database, come esemplificato di seguito:
1 public DbSet<NomeClasse> NomeTabella { get; set; }
Nota N4: la classe UtilizziPCContext
potrebbe, se richiesto, implementare i metodi:
1protected override void OnConfiguring(DbContextOptionsBuilder options);
2protected override void OnModelCreating(ModelBuilder modelBuilder);
Effettuare la migration del database e la creazione fisica del file di database di SQLite:
1dotnet ef migrations add InitialCreate
2dotnet ef database update
Creare un metodo static void PopulateDB()
nella classe Program
che inserisce:
La creazione del modello e del DBContext procede in maniera del tutto analoga a quanto già visto negli esempi precedenti.
Per il data Model i file sono:
File Classe.cs
:
1using Microsoft.EntityFrameworkCore;
2
3namespace DbUtilizziPC.Model;
4//https://learn.microsoft.com/en-us/ef/core/modeling/indexes?tabs=data-annotations#index-uniqueness
5[Index(nameof(Nome), IsUnique = true)]
6public class Classe
7{
8 public int Id { get; set; }
9 public string Nome { get; set; } = null!;
10 public string Aula { get; set; } = null!;
11 //navigation property
12 public ICollection<Studente> Studenti { get; set; } = null!;
13 public override string ToString()
14 {
15 return $"{{{nameof(Id)} = {Id}, {nameof(Nome)} = {Nome}, {nameof(Aula)} = {Aula}}}";
16 }
17}
Studente.cs
: 1namespace DbUtilizziPC.Model;
2
3public class Studente
4{
5 public int Id { get; set; }
6 public string Nome { get; set; } = null!;
7 public string Cognome { get; set; } = null!;
8 //Foreign Key su Classe
9 public int ClasseId { get; set; }
10 //Navigation Property su Classe
11 public Classe Classe { get; set; } = null !;
12 //navigation property per Molti a Molti
13 public List<Utilizza> Utilizzi { get; } = null!;
14 //Skip Navigation Property
15 public List<Computer> Computers { get; } = null!;
16 public override string ToString()
17 {
18 return $"{{{nameof(Id)} = {Id}, {nameof(Nome)} = {Nome}, {nameof(Cognome)} = {Cognome}, {nameof(Classe)} = {Classe}}}";
19 }
20}
Computer.cs
: 1namespace DbUtilizziPC.Model;
2
3public class Computer
4{
5 public int Id { get; set; }
6 public string Modello { get; set; } = null!;
7 public string Collocazione { get; set; } = null!;
8 public List<Utilizza> Utilizzi { get; } = null!;
9 //Skip Navigation Property
10 public List<Studente> Studenti { get; } = null!;
11 public override string ToString()
12 {
13 return $"{{{nameof(Id)} = {Id}, {nameof(Modello)} = {Modello}, {nameof(Collocazione)} = {Collocazione}}}";
14 }
15}
Utilizza.cs
: 1using Microsoft.EntityFrameworkCore;
2
3namespace DbUtilizziPC.Model;
4
5[PrimaryKey(nameof(StudenteId), nameof(ComputerId), nameof(DataOraInizioUtilizzo))]
6public class Utilizza
7{
8 public int StudenteId { get; set; }
9 public Studente Studente { get; set; } = null!;
10 public int ComputerId { get; set; }
11 public Computer Computer { get; set; } = null!;
12 public DateTime DataOraInizioUtilizzo { get; set; }
13 public DateTime? DataOraFineUtilizzo { get; set; }
14
15 public override string ToString()
16 {
17 return $"{{{nameof(StudenteId)} = {StudenteId}, {nameof(ComputerId)} = {ComputerId}," +
18 $" {nameof(DataOraInizioUtilizzo)} = {DataOraInizioUtilizzo}," +
19 $" {nameof(DataOraFineUtilizzo)} = {DataOraFineUtilizzo}}}";
20 }
21}
Il DbContext è definito attraverso il file UtilizziPCContext.cs
relativo alla definizione della classe UtilizziPCContext
:
File UtilizziPCContext.cs
:
1using DbUtilizziPC.Model;
2using Microsoft.EntityFrameworkCore;
3
4namespace DbUtilizziPC.Data;
5public class UtilizziPCContext:DbContext
6{
7 public DbSet<Classe> Classi { get; set; } = null!;
8 public DbSet<Studente> Studenti { get; set; } = null!;
9 public DbSet<Computer> Computers { get; set; } = null!;
10 public DbSet<Utilizza> Utilizzi { get; set; } = null!;
11 public string DbPath { get; }
12 public UtilizziPCContext()
13 {
14 var cartellaApp = AppContext.BaseDirectory;
15 DbPath = Path.Combine(cartellaApp, "../../../DbUtilizziPC.db");
16 }
17 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
18 {
19 optionsBuilder.UseSqlite($"Data Source = {DbPath}");
20 }
21 protected override void OnModelCreating(ModelBuilder modelBuilder)
22 {
23 modelBuilder.Entity<Studente>()
24 .HasMany(s => s.Computers)
25 .WithMany(c => c.Studenti)
26 .UsingEntity<Utilizza>(
27 //left => left.HasOne(u => u.Computer).WithMany(c => c.Utilizzi).HasForeignKey(u => u.ComputerId),
28 //right => right.HasOne(u => u.Studente).WithMany(s => s.Utilizzi).HasForeignKey(u => u.StudenteId),
29 //k =>k.HasKey( u => new {u.ComputerId, u.StudenteId, u.DataOraInizioUtilizzo })
30 );
31 }
32}
Dopo aver fatto la migration e la creazione del database, si scriva il codice delle query seguenti, espresse mediante metodi:Q1(string classe)
: Stampa a Console il numero di alunni della classe in input; ad esempio, stampa il numero di alunni della 4IAQ2()
: Stampa a Console il numero di alunni per ogni classeQ3()
: Stampa gli studenti che non hanno ancora restituito i computer (sono quelli collegati a Utilizza con DataOraFineUtilizzo pari a null)Q4(string classe)
: Stampa l’elenco dei computer che sono stati utilizzati dagli studenti della classe specificata in input. Ad esempio, stampare l’elenco dei computer utilizzati dalla 4IA. Non mostrare ripetizioni nella stampa.Q5(int computerId)
: Dato un computer (di cui si conosce l’Id) riporta l’elenco degli studenti che lo hanno usato negli ultimi 30 giorni, con l’indicazione della DataOraInizioUtilizzo, ordinando i risultati per classe e, a parità di classe, per data (mostrando prima le date più recenti)Q6()
: Stampa per ogni classe quanti utilizzi di computer sono stati fatti negli ultimi 30 giorni.Q7()
: Stampa la classe che ha utilizzato maggiormente i computer (quella con il maggior numero di utilizzi) negli ultimi 30 giorni.
File Program.cs
:
1using DbUtilizziPC.Data;
2using DbUtilizziPC.Model;
3using Microsoft.EntityFrameworkCore;
4using Microsoft.EntityFrameworkCore.Infrastructure;
5using Microsoft.EntityFrameworkCore.Storage;
6using System.Text;
7
8namespace DbUtilizziPC;
9internal class Program
10{
11 enum ModalitàOperativa
12 {
13 CreazioneDb,
14 Q1,
15 Q2,
16 Q3,
17 Q4,
18 Q5,
19 Q6,
20 Q7,
21 Nessuna,
22 CancellazioneDb
23 }
24
25 static void Main(string[] args)
26 {
27 Console.OutputEncoding = Encoding.UTF8;
28 Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("it-IT");
29 //colore di default della console
30 Console.ForegroundColor = ConsoleColor.Cyan;
31 //gestione del menu
32 bool uscitaDalProgramma;
33 do
34 {
35 //gestione della scelta
36 bool correctInput;
37 do
38 {
39 Console.Write("Inserire la modalità operativa [");
40 foreach(var elem in Enum.GetNames(typeof(ModalitàOperativa)))
41 {
42 Console.Write(elem+", ");
43 }
44 Console.WriteLine("]");
45 correctInput = Enum.TryParse(Console.ReadLine(), true, out ModalitàOperativa modalitàOperativa);
46 if (correctInput)
47 {
48 switch (modalitàOperativa)
49 {
50 case ModalitàOperativa.CreazioneDb:
51 //using effettua il Dispose del context automaticamente
52 using(UtilizziPCContext db = new ())
53 {
54 CreazioneDb(db);
55 }
56 break;
57 case ModalitàOperativa.Q1:
58 Console.WriteLine("Q1: Contare gli alunni di una classe");
59 Console.WriteLine("Inserire la classe");
60 string? classe = Console.ReadLine()?.ToUpper();
61 if(classe != null)
62 {
63 using UtilizziPCContext db = new();
64 Q1(classe, db);
65 }
66 break;
67 case ModalitàOperativa.Q2:
68 Console.WriteLine("Q2: Riportare il numero di alunni per ogni classe");
69 using (UtilizziPCContext db = new())
70 {
71 Q2(db);
72 }
73 break;
74 case ModalitàOperativa.Q3:
75 Console.WriteLine("Q3: Stampa gli studenti che non hanno ancora restituito i computer (sono quelli collegati a Utilizza con DataOraFineUtilizzo pari a null)");
76 using (UtilizziPCContext db = new())
77 {
78 Q3(db);
79 }
80 break;
81 case ModalitàOperativa.Q4:
82 Console.WriteLine("Q4: Stampa l’elenco dei computer che sono stati utilizzati dagli studenti della classe specificata in input. ");
83 Console.WriteLine("Inserire la classe");
84 classe = Console.ReadLine()?.ToUpper();
85 if (classe != null)
86 {
87 using UtilizziPCContext db = new();
88 Q4(classe, db);
89 }
90 break;
91 case ModalitàOperativa.Q5:
92 Console.WriteLine("Q5: Dato un computer (di cui si conosce l’Id) riporta l’elenco degli studenti che lo hanno usato negli ultimi 30 giorni, con l'indicazione della DataOraInizioUtilizzo," +
93 "ordinando i risultati per classe e, a parità di classe, per data (mostrando prima le date più recenti)");
94 Console.WriteLine("Inserire l'id del computer");
95 bool correctId = int.TryParse(Console.ReadLine(), out int computerId) && computerId>=0;
96 if (correctId)
97 {
98 using UtilizziPCContext db = new();
99 Q5(computerId, db);
100 }
101 break;
102 case ModalitàOperativa.Q6:
103 Console.WriteLine("Q6: Stampa per ogni classe quanti utilizzi di computer sono stati fatti negli ultimi 30 giorni.");
104 using (UtilizziPCContext db = new())
105 {
106 Q6(db);
107 }
108 break;
109 case ModalitàOperativa.Q7:
110 Console.WriteLine("Q7: Stampa le classi che hanno utilizzato maggiormente i computer (quelle con il maggior numero di utilizzi) " +
111 "negli ultimi 30 giorni");
112 using (UtilizziPCContext db = new())
113 {
114 Q7(db);
115 }
116 break;
117 case ModalitàOperativa.CancellazioneDb:
118 using (UtilizziPCContext db = new())
119 {
120 CancellazioneDb(db);
121 }
122 break;
123 default:
124 WriteLineWithColor("Non è stata impostata nessuna modalità operativa", ConsoleColor.Yellow);
125 break;
126 }
127 }
128 if (!correctInput)
129 {
130 Console.Clear();
131 WriteLineWithColor("Il valore inserito non corrisponde a nessuna opzione valida.\nI valori ammessi sono: [Creazione, Lettura, Modifica, Cancellazione, Nessuna]", ConsoleColor.Red);
132 }
133 } while (!correctInput);
134 Console.WriteLine("Uscire dal programma?[Si, No]");
135 uscitaDalProgramma = Console.ReadLine()?.ToLower().StartsWith("si") ?? false;
136 Console.Clear();
137 } while (!uscitaDalProgramma);
138 }
139 static void CreazioneDb(UtilizziPCContext db)
140 {
141 //verifichiamo se il database esista già
142 //https://medium.com/@Usurer/ef-core-check-if-db-exists-feafe6e36f4e
143 //https://stackoverflow.com/questions/33911316/entity-framework-core-how-to-check-if-database-exists
144 if (db.Database.GetService<IRelationalDatabaseCreator>().Exists())
145 {
146 WriteLineWithColor("Il database esiste già, vuoi ricrearlo da capo? Tutti i valori precedentemente inseriti verranno persi. [Si, No]", ConsoleColor.Red);
147 bool dbErase = Console.ReadLine()?.ToLower().StartsWith("si") ?? false;
148 if (dbErase)
149 {
150 //cancelliamo il database se esiste
151 db.Database.EnsureDeleted();
152 //ricreiamo il database a partire dal model (senza dati --> tabelle vuote)
153 db.Database.EnsureCreated();
154 //inseriamo i dati nelle tabelle
155 PopulateDb(db);
156 }
157 }
158 else //il database non esiste
159 {
160 //ricreiamo il database a partire dal model (senza dati --> tabelle vuote)
161 db.Database.EnsureCreated();
162 //popoliamo il database
163 PopulateDb(db);
164 }
165
166 static void PopulateDb(UtilizziPCContext db)
167 {
168 //Creazione dei Clienti - gli id vengono generati automaticamente come campi auto-incremento quando si effettua l'inserimento, tuttavia
169 //è bene inserire esplicitamente l'id degli oggetti quando si procede all'inserimento massivo gli elementi mediante un foreach perché
170 //EF core potrebbe inserire nel database gli oggetti in un ordine diverso rispetto a quello del foreach
171 // https://stackoverflow.com/a/54692592
172 // https://stackoverflow.com/questions/11521057/insertion-order-of-multiple-records-in-entity-framework/
173 List<Classe> classi =
174 [
175 new (){Id =1, Nome="3IA", Aula="Est 1"},
176 new (){Id =2,Nome="4IA", Aula="A32"},
177 new (){Id =3,Nome="5IA", Aula="A31"},
178 new (){Id =4,Nome="3IB", Aula="Est 2"},
179 new (){Id =5,Nome="4IB", Aula="A30"},
180 new (){Id =6,Nome="5IB", Aula="A32"},
181 ];
182
183 List<Studente> studenti =
184 [
185 new (){Id = 1, Nome = "Mario", Cognome = "Rossi", ClasseId =1 },
186 new (){Id = 2, Nome = "Giovanni", Cognome = "Verdi", ClasseId =1 },
187 new (){Id = 3, Nome = "Piero", Cognome = "Angela", ClasseId = 1 },
188 new (){Id = 4, Nome = "Leonardo", Cognome = "Da Vinci", ClasseId = 1 },
189 new (){Id = 50, Nome = "Cristoforo", Cognome = "Colombo", ClasseId=2 },
190 new (){Id = 51, Nome = "Piero", Cognome = "Della Francesca", ClasseId=2 },
191 new (){Id = 82, Nome = "Alessandro", Cognome = "Manzoni", ClasseId=4 },
192 new (){Id = 83, Nome = "Giuseppe", Cognome = "Parini", ClasseId=4 },
193 new (){Id = 102, Nome = "Giuseppe", Cognome = "Ungaretti", ClasseId=3 },
194 new (){Id = 103, Nome = "Luigi", Cognome = "Pirandello", ClasseId=3 },
195 new (){Id = 131, Nome = "Enrico", Cognome = "Fermi", ClasseId=6 },
196 new (){Id = 132, Nome = "Sandro", Cognome = "Pertini", ClasseId=6 },
197 ];
198
199 List<Computer> computers =
200 [
201 new (){Id = 1, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D1-D5"},
202 new (){Id = 2, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D1-D5"},
203 new (){Id = 3, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D1-D5"},
204 new (){Id = 4, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D1-D5"},
205 new (){Id = 5, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D1-D5"},
206 new (){Id = 6, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D6-D10"},
207 new (){Id = 7, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D6-D10"},
208 new (){Id = 8, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D6-D10"},
209 new (){Id = 9, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D6-D10"},
210 new (){Id = 10, Modello="Hp 19 inc. 2019", Collocazione = "Bunker-D6-D10"},
211 new (){Id = 20, Modello="Lenovo i5 2020", Collocazione = "Bunker-D20-D25"},
212 new (){Id = 21, Modello="Lenovo i5 2020", Collocazione = "Bunker-D20-D25"},
213 new (){Id = 22, Modello="Lenovo i5 2020", Collocazione = "Bunker-D20-D25"},
214 new (){Id = 23, Modello="Lenovo i5 2020", Collocazione = "Bunker-D20-D25"},
215 new (){Id = 24, Modello="Lenovo i5 2020", Collocazione = "Bunker-D20-D25"},
216 new (){Id = 61, Modello="Lenovo i5 2021", Collocazione = "Carrello-Mobile-S1"},
217 new (){Id = 62, Modello="Lenovo i5 2021", Collocazione = "Carrello-Mobile-S2"},
218 new (){Id = 63, Modello="Lenovo i5 2021", Collocazione = "Carrello-Mobile-S3"},
219 new (){Id = 64, Modello="Lenovo i5 2021", Collocazione = "Carrello-Mobile-S4"},
220 new (){Id = 65, Modello="Lenovo i5 2021", Collocazione = "Carrello-Mobile-S5"},
221 ];
222
223 List<Utilizza> utilizzi = new()
224 {
225 new (){ComputerId = 61,StudenteId=1,
226 DataOraInizioUtilizzo = DateTime.Now.Add(- new TimeSpan(1,12,0)),
227 DataOraFineUtilizzo = DateTime.Now},
228 new (){ComputerId = 61,StudenteId=1,
229 DataOraInizioUtilizzo = DateTime.Now.Add(- new TimeSpan(1,1,12,0)),
230 DataOraFineUtilizzo = DateTime.Now.Add(- new TimeSpan(1,0,0,0))},
231 new (){ComputerId = 61,StudenteId=3,
232 DataOraInizioUtilizzo = DateTime.Today.AddDays(-2).AddHours(11),
233 DataOraFineUtilizzo = DateTime.Today.AddDays(-2).AddHours(12)},
234 new (){ComputerId = 61,StudenteId=82,
235 DataOraInizioUtilizzo = DateTime.Today.AddDays(-1).AddHours(12),
236 DataOraFineUtilizzo = DateTime.Today.AddDays(-1).AddHours(13) },
237 new (){ComputerId = 61,StudenteId=1,
238 DataOraInizioUtilizzo = DateTime.Today.AddHours(11),
239 DataOraFineUtilizzo = DateTime.Today.AddHours(12) },
240 new (){ComputerId = 62,StudenteId=2,
241 DataOraInizioUtilizzo = DateTime.Today.AddDays(-2).AddHours(11),
242 DataOraFineUtilizzo = DateTime.Today.AddDays(-2).AddHours(12) },
243 new (){ComputerId = 62,StudenteId=2,
244 DataOraInizioUtilizzo = DateTime.Today.AddDays(-1).AddHours(12),
245 DataOraFineUtilizzo = DateTime.Today.AddDays(-1).AddHours(13) },
246 new (){ComputerId = 62,StudenteId=4,
247 DataOraInizioUtilizzo = DateTime.Today.AddHours(11),
248 DataOraFineUtilizzo = DateTime.Today.AddHours(11) },
249 new (){ComputerId = 1,StudenteId=50,
250 DataOraInizioUtilizzo = DateTime.Today.AddDays(-2).AddHours(11),
251 DataOraFineUtilizzo = DateTime.Today.AddDays(-2).AddHours(12) },
252 new (){ComputerId = 1,StudenteId=103,
253 DataOraInizioUtilizzo = DateTime.Today.AddDays(-1).AddHours(12),
254 DataOraFineUtilizzo = DateTime.Today.AddDays(-1).AddHours(13) },
255 new (){ComputerId = 1,StudenteId=50,
256 DataOraInizioUtilizzo = DateTime.Today.AddHours(11),
257 DataOraFineUtilizzo = DateTime.Today.AddHours(12) },
258 new (){ComputerId = 2,StudenteId=51,
259 DataOraInizioUtilizzo = DateTime.Today.AddDays(-1).AddHours(11),
260 DataOraFineUtilizzo = DateTime.Today.AddDays(-1).AddHours(12) },
261 new (){ComputerId = 2,StudenteId=51,
262 DataOraInizioUtilizzo = DateTime.Today.AddDays(-1).AddHours(12),
263 DataOraFineUtilizzo = DateTime.Today.AddDays(-1).AddHours(13) },
264 new (){ComputerId = 2,StudenteId=103,
265 DataOraInizioUtilizzo = DateTime.Today.AddHours(11),
266 DataOraFineUtilizzo = DateTime.Today.AddHours(12) },
267 new (){ComputerId = 3,StudenteId=82,
268 DataOraInizioUtilizzo = DateTime.Today.AddDays(-2).AddHours(11),
269 DataOraFineUtilizzo = DateTime.Today.AddDays(-2).AddHours(12) },
270 new (){ComputerId = 3,StudenteId=82,
271 DataOraInizioUtilizzo = DateTime.Today.AddDays(-1).AddHours(11),
272 DataOraFineUtilizzo = DateTime.Today.AddDays(-1).AddHours(13) },
273 new (){ComputerId = 3,StudenteId=83,
274 DataOraInizioUtilizzo = DateTime.Today.AddHours(11),
275 DataOraFineUtilizzo = DateTime.Today.AddHours(12) },
276 new (){ComputerId = 20,StudenteId=102,
277 DataOraInizioUtilizzo = DateTime.Today.AddDays(-2).AddHours(11),
278 DataOraFineUtilizzo = DateTime.Today.AddDays(-2).AddHours(12) },
279 new (){ComputerId = 20,StudenteId=103,
280 DataOraInizioUtilizzo = DateTime.Today.AddDays(-1).AddHours(11),
281 DataOraFineUtilizzo = DateTime.Today.AddDays(-1).AddHours(12) },
282 new (){ComputerId = 20,StudenteId=103,
283 DataOraInizioUtilizzo = DateTime.Today.AddHours(11),
284 DataOraFineUtilizzo = DateTime.Today.AddHours(12) },
285 new (){ComputerId = 64,StudenteId=131,
286 DataOraInizioUtilizzo = DateTime.Now.Add(- new TimeSpan(0,12,0)),
287 DataOraFineUtilizzo = null},
288 new (){ComputerId = 65,StudenteId=132,
289 DataOraInizioUtilizzo = DateTime.Now.Add(- new TimeSpan(1,12,0)),
290 DataOraFineUtilizzo = null},
291 };
292 Console.WriteLine("Inseriamo le classi nel database");
293 classi.ForEach(c => db.Add(c));
294 db.SaveChanges();
295 Console.WriteLine("Inseriamo gli studenti nel database");
296 studenti.ForEach(s => db.Add(s));
297 db.SaveChanges();
298 Console.WriteLine("Inseriamo i computers nel database");
299 computers.ForEach(c => db.Add(c));
300 db.SaveChanges();
301 Console.WriteLine("Inseriamo gli utilizzi nel database");
302 utilizzi.ForEach(u => db.Add(u));
303 db.SaveChanges();
304 }
305 }
306 //Q1: Contare gli alunni di una classe
307 private static void Q1(string classe, UtilizziPCContext db)
308 {
309 //esiste al più una sola classe con un dato nome
310 Classe? laClasse = db.Classi.Where(c => c.Nome==classe).FirstOrDefault();
311 if (laClasse != null)
312 {
313 int numeroStudentiDellaClasse = db.Studenti.Where(s => s.ClasseId == laClasse.Id).Count();
314 Console.WriteLine($"Il numero di studenti della classe {classe} è {numeroStudentiDellaClasse} ");
315 }
316 else
317 {
318 Console.WriteLine("Il nome fornito non corrisponde a nessuna classe");
319 }
320 }
321
322 //Q2: Riportare il numero di alunni per ogni classe
323 private static void Q2(UtilizziPCContext db)
324 {
325 //raggruppiamo gli studenti per classe e poi contiamo
326 db.Studenti
327 .GroupBy(s => s.Classe) //Classe è la navigation Property
328 .Select(g => new { g.Key, NumeroStudenti = g.Count() })//Key è un oggetto di tipo Classe
329 .ToList()
330 .ForEach(g => Console.WriteLine($"Classe = {g.Key.Nome}, Numero Alunni = {g.NumeroStudenti}"));
331 }
332
333 //Q3: Stampa gli studenti che non hanno ancora restituito i computer (sono quelli collegati a Utilizza con DataOraFineUtilizzo pari a null)
334 private static void Q3(UtilizziPCContext db)
335 {
336 Console.WriteLine("Versione con Navigation Property");
337 db.Utilizzi
338 .Where(u => u.DataOraFineUtilizzo == null)
339 //qui bisogna esplicitamente indicare ad EF Core di caricare anche la Classe di uno Studente
340 .Include(u => u.Studente.Classe)
341 .Select(u => u.Studente)
342 .ToList()
343 .ForEach(Console.WriteLine);
344
345 Console.WriteLine("Versione con Join");
346 db.Utilizzi
347 .Where(u => u.DataOraFineUtilizzo == null)
348 .Join(db.Studenti,
349 u => u.StudenteId,
350 s => s.Id,
351 (u, s) => s)
352 .Include(s => s.Classe)
353 .ToList()
354 .ForEach(Console.WriteLine);
355 }
356
357 //Q4: Stampa l’elenco dei computer che sono stati utilizzati dagli studenti della classe specificata in input.
358 private static void Q4(string classe, UtilizziPCContext db)
359 {
360 Console.WriteLine("Versione con Skip Navigation Property");
361 db.Studenti
362 .Where(s => s.Classe.Nome == classe)
363 //in questo caso ad ogni studente è associata una lista di computer.
364 //Per restituire una sola lista e non una lista di liste usare SelectMany
365 .SelectMany(s => s.Computers)
366 .Distinct()
367 .ToList()
368 .ForEach(Console.WriteLine);
369
370 Console.WriteLine("Versione con Join");
371 db.Studenti
372 .Where(s => s.Classe.Nome == classe)
373 .Join(db.Utilizzi,
374 s => s.Id,
375 u => u.StudenteId,
376 (s, u) => new { u.ComputerId })
377 .Join(db.Computers,
378 u => u.ComputerId,
379 c => c.Id,
380 (u, c) => c)
381 .Distinct()
382 .ToList()
383 .ForEach(Console.WriteLine);
384 }
385
386 //Q5: Dato un computer (di cui si conosce l’Id) riporta l’elenco degli studenti che lo hanno usato negli ultimi 30 giorni,
387 //con l'indicazione della DataOraInizioUtilizzo,ordinando i risultati per classe e, a parità di classe, per data
388 //(mostrando prima le date più recenti)
389 private static void Q5(int computerId, UtilizziPCContext db)
390 {
391 Console.WriteLine("Versione con Navigation Property");
392 db.Utilizzi
393 .Where(u => u.ComputerId == computerId && u.DataOraInizioUtilizzo >= DateTime.Now.AddDays(-30))
394 .Include(u => u.Studente)
395 .ThenInclude(s => s.Classe)
396 .OrderBy(u => u.Studente.Classe.Nome)
397 .ThenByDescending(u => u.DataOraInizioUtilizzo)
398 .ToList()
399 .ForEach(u => Console.WriteLine($"Studente = {u.Studente}, Data utilizzo computer = {u.DataOraInizioUtilizzo}"));
400
401 Console.WriteLine("Versione con Skip Navigation - in questo caso vogliamo solo sapere quali sono gli studenti che hanno usato il computer");
402 db.Computers
403 .Where(c => c.Id == computerId)
404 .Include(c => c.Studenti)
405 .FirstOrDefault()?
406 .Studenti
407 .ToList()
408 .ForEach(Console.WriteLine);
409
410 Console.WriteLine("Versione con Join");
411 db.Utilizzi
412 .Where(u => u.ComputerId == computerId && u.DataOraInizioUtilizzo >= DateTime.Now.AddDays(-30))
413 .Join(db.Studenti.Include(s => s.Classe),
414 u => u.StudenteId,
415 s => s.Id,
416 (u, s) => new { s, u.DataOraInizioUtilizzo })
417 .OrderBy(a => a.s.Classe.Nome)
418 .ThenByDescending(a => a.DataOraInizioUtilizzo)
419 .ToList()
420 .ForEach(a => Console.WriteLine($"Studente = {a.s}, Data utilizzo computer = {a.DataOraInizioUtilizzo}"));
421 }
422
423 //Q6: Stampa per ogni classe quanti utilizzi di computer sono stati fatti negli ultimi 30 giorni.
424 private static void Q6(UtilizziPCContext db)
425 {
426 Console.WriteLine("Versione con Navigation Property");
427 db.Utilizzi
428 .Where(u => u.DataOraInizioUtilizzo >= DateTime.Now.AddDays(-30))
429 .GroupBy(u => u.Studente.Classe.Nome)
430 .Select(g => new { NomeClasse = g.Key, NumeroUtilizzi = g.Count() })
431 .ToList()
432 .ForEach(Console.WriteLine);
433
434 Console.WriteLine("Versione con Join");
435 db.Studenti.Join(db.Utilizzi.Where(u => u.DataOraInizioUtilizzo >= DateTime.Now.AddDays(-30)),
436 s => s.Id,
437 u => u.StudenteId,
438 (s, u) => new { NomeClasse = s.Classe.Nome, u })
439 .GroupBy(a => a.NomeClasse)
440 .Select(g => new { NomeClasse = g.Key, NumeroUtilizzi = g.Count() })
441 .ToList()
442 .ForEach(Console.WriteLine);
443 }
444
445 //Q7: Stampa le classi che hanno utilizzato maggiormente i computer (quelle con il maggior numero di utilizzi) negli ultimi 30 giorni
446 private static void Q7(UtilizziPCContext db)
447 {
448 Console.WriteLine("Versione con Navigation Property");
449 var nUtilizziPerClasse = db.Utilizzi
450 .Where(u => u.DataOraInizioUtilizzo >= DateTime.Now.AddDays(-30))
451 .GroupBy(u => u.Studente.Classe)
452 .Select(g => new { NomeClasse = g.Key.Nome, NumeroUtilizzi = g.Count() });
453 var classiConNumeroUtilizziMax = nUtilizziPerClasse
454 .Where(u => u.NumeroUtilizzi == nUtilizziPerClasse.Max(u => u.NumeroUtilizzi));
455 classiConNumeroUtilizziMax
456 .ToList()
457 .ForEach(Console.WriteLine);
458
459 Console.WriteLine("Versione con Join");
460 //con una prima query sul Database calcoliamo il numero di utilizzi per classe
461 //Il risultato è un oggetto che implementa IQueryable sul database
462 var numeroUtilizziPerClasse = db.Studenti
463 .Join(db.Utilizzi.Where(u => u.DataOraInizioUtilizzo >= DateTime.Now.AddDays(-30)),
464 s => s.Id,
465 u => u.StudenteId,
466 (s, u) => new { NomeClasse = s.Classe.Nome, u })
467 .GroupBy(a => a.NomeClasse)
468 .Select(g => new { NomeClasse = g.Key, NumeroUtilizzi = g.Count() });
469
470 //Con una seconda query sul Database troviamo le classi che hanno il numero massimo di utilizzi
471 var classiConNumeroUtilizziMassimo = numeroUtilizziPerClasse
472 .Where(u => u.NumeroUtilizzi == numeroUtilizziPerClasse.Max(u => u.NumeroUtilizzi));
473 //stampiamo il risultato nell'applicazione
474 classiConNumeroUtilizziMassimo
475 .ToList()
476 .ForEach(Console.WriteLine);
477 }
478 static void CancellazioneDb(UtilizziPCContext db)
479 {
480 WriteLineWithColor("Attenzione, tutto il database andrà eliminato! Questa operazione non può essere revocata.\nSei sicuro di voler procedere? [Si, No]", ConsoleColor.Red);
481 bool dbErase = Console.ReadLine()?.ToLower().StartsWith("si") ?? false;
482 if (dbErase)
483 {
484 if (db.Database.EnsureDeleted())
485 {
486 Console.WriteLine("Database cancellato correttamente");
487 }
488 else
489 {
490 Console.WriteLine("Il database non esisteva e non è stata fatta alcuna azione");
491 }
492 }
493 }
494 //Stampa a console con il colore di foreground selezionato e successivamente ripristina il colore precedente
495 static void WriteLineWithColor(string text, ConsoleColor consoleColor)
496 {
497 ConsoleColor previousColor = Console.ForegroundColor;
498 Console.ForegroundColor = consoleColor;
499 Console.WriteLine(text);
500 Console.ForegroundColor = previousColor;
501 }
502}
Creare un’applicazione .Net Core
per la gestione dei corsi di laurea universitari.
Usando il framework EF Core creare il database dei corsi di laurea e degli studenti, sapendo che nel model sono previste le seguenti classi:
Studente (
Matricola
, Nome, Cognome, CorsoLaureaId*, AnnoNascita )
CorsoLaurea(
CorsoLaureaId
, TipoLaurea, Facoltà )
Frequenta (
Matricola*
,CodCorso*
)
Corso (
CodiceCorso
, Nome, CodDocente* )
Docente(
CodDocente
, Nome, Cognome, Dipartimento )
Dove:
Medicina
, Ingegneria
, MatematicaFisicaScienze
, Economia
Dipartimento
è un enumerativo che contiene i valori: Matematica
, Fisica
, Economia
, IngegneriaCivile
, IngegneriaInformatica
, Medicina
.TipoLaurea
è un enumerativo che contiene i valori: Triennale
, Magistrale
Nello schema precedente si è usata la convenzione di indicare la chiave primaria con attributi sottolineati, mentre la chiave esterna è indicata con un asterisco.
La creazione del modello e del DBContext procede in maniera del tutto analoga a quanto già visto negli esempi precedenti.
Per il data Model i file sono:
File Corso.cs
:
1using System.ComponentModel.DataAnnotations.Schema;
2using System.ComponentModel.DataAnnotations;
3
4namespace Universita.Model;
5public class Corso
6{
7 //qui l'annotation è resa necessaria perché non si segue la convenzione sul nome della chiave
8 [Key]
9 public int CodiceCorso { get; set; }
10 public string Nome { get; set; } = null!;
11 //qui l'annotation è resa necessaria perché non si segue la convenzione sul nome della chiave
12 public int? CodDocente { get; set; }
13 //NAVIGATION PROPERTY
14 [ForeignKey("CodDocente")]
15 public Docente? Docente { get; set; }
16 //NAVIGATION PROPERTY PER MOLTI A MOLTI STUDENTI - CORSI
17 //https://learn.microsoft.com/en-us/ef/core/modeling/relationships/navigations
18 public ICollection<Frequenta> Frequenze { get; set; } = null!;
19 //skip navigation - https://learn.microsoft.com/en-us/ef/core/modeling/relationships/many-to-many
20 public ICollection<Studente> Studenti { get; set; } = null!;
21
22}
23
CorsoLaurea.cs
: 1namespace Universita.Model;
2public class CorsoLaurea
3{
4 //Chiave primaria
5 public int CorsoLaureaId { get; set; }
6 public TipoLaurea TipoLaurea { get; set; }
7 public Facoltà Facoltà { get; set; }
8 //NAVIGATION PROPERTY
9 public ICollection<Studente> Studenti { get; set; } = null!;
10}
11
Docente.cs
: 1using System.ComponentModel.DataAnnotations;
2
3namespace Universita.Model;
4public class Docente
5{
6 //qui l'annotation è resa necessaria perché non si segue la convenzione sul nome della chiave
7 [Key]
8 public int CodDocente { get; set; }
9 public string Nome { get; set; } = null!;
10 public string Cognome { get; set; } = null!;
11 public Dipartimento Dipartimento { get; set; }
12 //NAVIGATION PROPERTY
13 public ICollection<Corso> Corsi { get; set; } = null!;
14}
15
Frequenta.cs
: 1namespace Universita.Model;
2
3public class Frequenta
4{
5 //la chiave primaria è composta da Matricola e CodCorso tramite Fluent API
6 //le chiavi esterne vanno configurate mediante Fluent API
7 public int Matricola { get; set; }
8 //NAVIGATION PROPERTY
9 public Studente Studente { get; set; } = null!;
10 public int CodCorso { get; set; }
11 //NAVIGATION PROPERTY
12 public Corso Corso { get; set; } = null!;
13
14
15}
SharedEnums.cs
: 1namespace Universita.Model;
2public enum Facoltà
3{
4 Medicina,
5 Ingegneria,
6 MatematicaFisicaScienze,
7 Economia
8}
9public enum Dipartimento
10{
11 Matematica,
12 Fisica,
13 Economia,
14 IngegneriaCivile,
15 IngegneriaInformatica,
16 Medicina
17}
18public enum TipoLaurea
19{
20 Triennale,
21 Magistrale
22}
23
Studente.cs
: 1using System.ComponentModel.DataAnnotations;
2
3namespace Universita.Model;
4
5public class Studente
6{
7 //qui l'annotation è resa necessaria perché non si segue la convenzione sul nome della chiave
8 [Key]
9 public int Matricola { get; set; }
10 public string Nome { get; set; } = null!;
11 public string Cognome { get; set; } = null!;
12 public int AnnoNascita { get; set; }
13
14 //Chiave esterna
15 public int CorsoLaureaId { get; set; }
16 //NAVIGATION PROPERTY PER CHIAVE ESTERNA
17 public CorsoLaurea CorsoLaurea { get; set; } = null!;
18
19 //NAVIGATION PROPERTY PER MOLTI A MOLTI STUDENTI - CORSI
20 public ICollection<Frequenta> Frequenze { get; set; } = null!;
21 //skip navigation - https://learn.microsoft.com/en-us/ef/core/modeling/relationships/many-to-many
22 public ICollection<Corso> Corsi { get; set; } = null!;
23
24}
25
Il DbContext è definito attraverso il file UniversitàContext.cs
relativo alla definizione della classe UniversitàContext
:
File UniversitàContext.cs
:
1using Microsoft.EntityFrameworkCore;
2using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
3using Universita.Model;
4
5namespace Universita.Data;
6public class UniversitaContext : DbContext
7{
8 //creazione delle tabelle
9 public DbSet<Studente> Studenti { get; set; } = null!;
10 public DbSet<CorsoLaurea> CorsiLaurea { get; set; } = null!;
11 public DbSet<Frequenta> Frequenze { get; set; } = null!;
12 public DbSet<Corso> Corsi { get; set; } = null!;
13 public DbSet<Docente> Docenti { get; set; } = null!;
14 public string DbPath { get; }
15 public UniversitaContext()
16 {
17 var dir = AppContext.BaseDirectory;
18 var path = Path.Combine(dir, "../../../universita.db");
19 DbPath = path;
20 }
21 protected override void OnConfiguring(DbContextOptionsBuilder options)
22 => options.UseSqlite($"Data Source={DbPath}");
23 protected override void OnModelCreating(ModelBuilder modelBuilder)
24 {
25 //per gestire la conversione da Enumerativo a string:
26 //https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions
27 var converterTipoLaurea = new EnumToStringConverter<TipoLaurea>();
28 var converterFacolta = new EnumToStringConverter<Facoltà>();
29 var converterDipartimento = new EnumToStringConverter<Dipartimento>();
30 modelBuilder
31 .Entity<CorsoLaurea>()
32 .Property(cl => cl.TipoLaurea)
33 .HasConversion(converterTipoLaurea);
34 modelBuilder
35 .Entity<CorsoLaurea>()
36 .Property(cl => cl.Facoltà)
37 .HasConversion(converterFacolta);
38 modelBuilder
39 .Entity<Docente>()
40 .Property(d => d.Dipartimento)
41 .HasConversion(converterDipartimento);
42
43 //https://learn.microsoft.com/en-us/ef/core/modeling/relationships/many-to-many
44 modelBuilder.Entity<Corso>()
45 .HasMany(c => c.Studenti)
46 .WithMany(s => s.Corsi)
47 .UsingEntity<Frequenta>
48 (
49 //chiave esterna su Studente
50 left => left
51 .HasOne(fr => fr.Studente)
52 .WithMany(s => s.Frequenze)
53 .HasForeignKey(fr => fr.Matricola),
54 //chiave esterna su Corso
55 right => right
56 .HasOne(fr => fr.Corso)
57 .WithMany(c => c.Frequenze)
58 .HasForeignKey(fr => fr.CodCorso),
59 //primary key - in realtà, avendo associato le chiavi esterne, EF Core sarebbe in grado di creare
60 //la primary key a partire dalla combinazione delle due foreigh key. In questo caso particolare
61 //questa terza fluent API è ridondante, ma è riportata per mostrare come si potrebbe configurare manualmente
62 //la chiave primaria
63 k => k.HasKey(fr => new { fr.Matricola, fr.CodCorso }));
64
65 }
66
67}
68
Dopo aver fatto la Migration del database, si scriva il codice delle query seguenti:
Q1: Stampare l’elenco degli studenti
Q2: Stampare l’elenco dei corsi
Q3: Modificare il docente di un corso di cui è noto l’id
Q4: Stampare il numero di corsi seguiti dallo studente con id = 1
Q5: Stampare il numero di corsi seguiti dallo studente con Nome=“Giovanni” e Cognome =“Casiraghi”
Q6: Stampare il numero di corsi seguiti da ogni studente
Q7: Stampare i corsi seguiti dallo studente con Nome=“Piero” e Cognome =“Gallo”
File Program.cs
:
1using Microsoft.EntityFrameworkCore.Infrastructure;
2using Microsoft.EntityFrameworkCore.Storage;
3using Universita.Data;
4using Universita.Model;
5
6namespace Universita;
7
8class Program
9{
10 /// <summary>
11 /// Stampa a Console l'elenco degli studenti
12 /// </summary>
13 public static void PrintStudents()
14 {
15 using var db = new UniversitaContext();
16 //leggo gli studenti
17 List<Studente> studenti = [.. db.Studenti];
18 //stampo gli studenti
19 studenti.ForEach(s => Console.WriteLine($"Matricola = {s.Matricola}, Nome = {s.Nome}, Cognome = {s.Cognome}"));
20 }
21
22 /// <summary>
23 /// Stampa a console l'elenco dei corsi
24 /// </summary>
25 public static void PrintCourses()
26 {
27 //leggo gli studenti
28 using var db = new UniversitaContext();
29 List<Corso> corsi = [.. db.Corsi];
30 corsi.ForEach(s => Console.WriteLine($"CodCorso = {s.CodiceCorso}, Nome = {s.Nome}, CodDocente = {s.CodDocente}"));
31
32 }
33
34 /// <summary>
35 /// Un metodo che stampa i corsi seguiti da uno studente di cui si conosce nome e cognome
36 /// </summary>
37 /// <param name="nomeStudente">Nome dello studente</param>
38 /// <param name="cognomeStudente">Nome dello studente</param>
39 public static void PrintCorsiDiStudente(string nomeStudente, string cognomeStudente)
40 {
41 //trovare i corsi seguiti da uno studente - doppio join
42 using var db = new UniversitaContext();
43 //attenzione - potrebbero esserci casi di omonimia!
44 var corsiFrequentatiDaStudente = db.Studenti
45 .Where(s => s.Nome.ToUpper().Equals(nomeStudente.ToUpper())
46 && s.Cognome.ToUpper().Equals(cognomeStudente.ToUpper()))
47 .Join(db.Frequenze,
48 s => s.Matricola,
49 f => f.Matricola,
50 (s, f) => new { f.CodCorso });
51 //Console.WriteLine($"\nQuery eseguita sul database per {nameof(corsiFrequentatiDaStudente)} = {corsiFrequentatiDaStudente.ToQueryString()}\n");
52 var dettaglioCorsiFrequentati = corsiFrequentatiDaStudente
53 .Join(db.Corsi,
54 cf => cf.CodCorso,
55 c => c.CodiceCorso,
56 (cf, c) => c);
57 //Console.WriteLine($"\nQuery eseguita sul database per {nameof(dettaglioCorsiFrequentati)} = {dettaglioCorsiFrequentati.ToQueryString()}\n");
58 dettaglioCorsiFrequentati.ToList().ForEach(c => Console.WriteLine($"Nome Corso = {c.Nome}"));
59
60 Console.WriteLine("Altro metodo - uso di navigation property");
61 //altro metodo - navigation property
62 db.Studenti
63 .Where(s => s.Nome.ToUpper().Equals(nomeStudente.ToUpper())
64 && s.Cognome.ToUpper().Contains(cognomeStudente.ToUpper()))
65 //how to flatten a list of list in C#: https://stackoverflow.com/questions/1145558/linq-list-of-lists-to-single-list
66 .SelectMany(x => x.Corsi)
67 .ToList()
68 .ForEach(c => Console.WriteLine($"Nome Corso = {c.Nome}"));
69
70 }
71
72 /// <summary>
73 /// Stampa a console il numero di corsi seguiti da uno studente
74 /// </summary>
75 /// <param name="codStudente">Codice dello studente di cui si vuole contare i corsi</param>
76 public static void PrintNumeroCorsiDiStudente(int codStudente)
77 {
78 using var db = new UniversitaContext();
79 //uso della tabella di collegamento molti a molti - Frequenta in questo caso
80 Console.WriteLine("Uso della tabella di collegamento molti a molti - Frequenta");
81 var numeroCorsiFrequentatiDaStudente = db.Frequenze.Where(f => f.Matricola == codStudente).Count();
82 Console.WriteLine($"Numero corsi frequentati dallo studente con Matricola = {codStudente} " +
83 $"-> numero corsi: {numeroCorsiFrequentatiDaStudente}");
84
85 //Uso di collection property
86 Console.WriteLine("Uso di collection property");
87 db.Studenti.
88 Where(s => s.Matricola == codStudente)
89 .Select(s => new { s.Matricola, s.Nome, s.Cognome, NumeroCorsi = s.Corsi.Count })
90 .ToList()
91 .ForEach(s => Console.WriteLine($"Numero corsi frequentati dallo studente matricola {s.Matricola} {s.Nome} {s.Cognome} -> numero corsi: {s.NumeroCorsi}"));
92
93 //Uso di Entry per recuperare dati di una collection collegata a un oggetto già caricato in memoria
94 Console.WriteLine("Uso di Entry per recuperare dati di una collection collegata a un oggetto già caricato in memoria");
95 var studente = db.Studenti.Where(s => s.Matricola == codStudente).FirstOrDefault();
96 if (studente != null)
97 { //come effettuare una query su una collection property: https://www.entityframeworktutorial.net/EntityFramework4.3/explicit-loading-with-dbcontext.aspx
98 var numeroCorsiFrequentatiDaStudente2 = db.Entry(studente).Collection(s => s.Corsi).Query().Count();
99 Console.WriteLine($"Numero corsi frequentati dallo studente con Matricola = " +
100 $"{codStudente} -> numero corsi: {numeroCorsiFrequentatiDaStudente2}");
101 }
102
103 }
104
105 /// <summary>
106 /// Stampa a console il numero di corsi seguiti da uno studente
107 /// </summary>
108 /// <param name="nomeStudente"></param>
109 /// <param name="cognomeStudente"></param>
110 public static void PrintNumeroCorsiDiStudente(string nomeStudente, string cognomeStudente)
111 {
112 using var db = new UniversitaContext();
113 //https://docs.microsoft.com/en-us/ef/core/miscellaneous/collations-and-case-sensitivity
114 //https://medium.com/@bridgesquared/efcore-sqlite-case-insensitive-order-21371b256e5
115 //https://github.com/dotnet/efcore/issues/8033
116 //per SQLite la collation predefinita è case sensitive
117
118 //versione con join e group by
119 db.Studenti
120 .Where(s => s.Nome.ToUpper().Equals(nomeStudente.ToUpper())
121 && s.Cognome.ToUpper().Equals(cognomeStudente.ToUpper()))
122 .Join(db.Frequenze,
123 s => s.Matricola,
124 f => f.Matricola,
125 (s, f) => new { s.Matricola, s.Nome, s.Cognome, f.CodCorso })
126 .GroupBy(t => t.Matricola)
127 .Select(g => new { Matricola = g.Key, g.First().Nome, g.First().Cognome, NumeroCorsi = g.Count() })
128 .ToList()
129 .ForEach(s => Console.WriteLine($"Numero corsi frequentati dallo studente matricola " +
130 $"{s.Matricola} {s.Nome} {s.Cognome} -> numero corsi: {s.NumeroCorsi}"));
131
132 //Uso di collection property
133 //poiché vengono dati in input nome e cognome, ci potrebbero essere più studenti con lo stesso nome e cognome
134 //quindi il risultato dovrebbe riportare, per ciascuno il numero di conrsi seguiti
135
136 Console.WriteLine("Uso di collection property");
137 db.Studenti
138 .Where(s => s.Nome.ToUpper().Contains(nomeStudente.ToUpper())
139 && s.Cognome.ToUpper().Contains(cognomeStudente.ToUpper()))
140 .Select(s => new { s.Matricola, s.Nome, s.Cognome, NumeroCorsi = s.Corsi.Count })
141 .ToList()
142 .ForEach(s => Console.WriteLine($"Numero corsi frequentati dallo studente matricola " +
143 $"{s.Matricola} {s.Nome} {s.Cognome} -> numero corsi: {s.NumeroCorsi}"));
144 }
145
146 /// <summary>
147 /// Modifica il docente di un corso di cui è noto l’id
148 /// </summary>
149 /// <param name="codCorso">codice del corso</param>
150 /// <param name="nuovoCodDocente">nuovo codice del docente</param>
151 public static void ModificaDocenteCorso(int codCorso, int nuovoCodDocente)
152 {
153 using var db = new UniversitaContext();
154 //accedo al corso
155 var corso = db.Corsi.Find(codCorso);
156 //accedo al docente per verificare che esiste quel docente
157 var docente = db.Docenti.Find(nuovoCodDocente);
158 //se esiste il docente con il nuovoCodDocente aggiorno il corso
159 if (docente != null && corso != null)
160 {
161 corso.CodDocente = nuovoCodDocente;
162 db.SaveChanges();
163 Console.WriteLine("Aggiornamento effettuato");
164 }
165 else if (docente == null)
166 {
167 Console.WriteLine("Impossibile aggiornare, il docente con il codice specificato non esiste");
168 }
169 else if (corso == null)
170 {
171 Console.WriteLine("Impossibile aggiornare, il corso il codice specificato non esiste");
172 }
173 }
174
175 /// <summary>
176 /// Per ogni studente stampa il numero di corsi frequentati
177 /// </summary>
178 public static void PrintNumeroCorsiFrequentatiPerStudente()
179 {
180 //raggruppo su Frequenta per Matricola e poi faccio la join con Studente per avere i dati di ciascuno studente
181 using var db = new UniversitaContext();
182
183 //MOLTO IMPORTANTE: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes
184 //https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client
185 //https://stackoverflow.com/questions/58138556/client-side-groupby-is-not-supported
186 //https://stackoverflow.com/a/60778664
187 //The LINQ GroupBy is much different from the SQL GROUP BY statement:
188 //LINQ just divides the underlying collection into chunks depending on a key,
189 //while SQL additionally applies an aggregation function to condense each of these chunks down into a single value.
190 var grouped = db.Frequenze.GroupBy(f => f.Matricola).Select(e => new { e.Key, Count = e.Count() });
191 //vedere anche
192 //https://www.thinktecture.com/en/entity-framework-core/hidden-group-by-capabilities-in-3-0-part-1/
193 //https://www.thinktecture.com/en/entity-framework-core/hidden-group-by-capabilities-in-3-0-part-2/
194
195 //https://stackoverflow.com/questions/37527783/get-sql-code-from-an-entity-framework-core-iqueryablet
196 //https://stackoverflow.com/a/51583047
197 //richiede using Microsoft.EntityFrameworkCore;
198 //Console.WriteLine($"\nQuery eseguita sul database per {nameof(grouped)} = {grouped.ToQueryString()}\n");
199
200 var result = grouped
201 .Join(db.Studenti,
202 group => group.Key,
203 s => s.Matricola,
204 (group, s) => new { s.Matricola, s.Nome, s.Cognome, NumeroCorsiFrequentati = group.Count });
205 //Console.WriteLine($"\nQuery eseguita sul database per {nameof(result)} = {result.ToQueryString()}\n");
206 //stampa risultato
207 result.ToList().ForEach(r => Console.WriteLine(r));
208
209 ////IL SEGUENTE APPROCCIO NON FUNZIONA DA EC 3.X IN AVANTI
210 ////il problema si ha quando si tenta di passare dalla GroupBy al ToList
211 //var grouped2 = db.Frequenta.GroupBy(f => f.Matricola);
212 //var result2 = grouped2.Join(db.Studenti,
213 // group => group.Key,
214 // s => s.Matricola,
215 // (group, s) => new { s.Matricola, s.Nome, s.Cognome, NumeroCorsiFrequentati = group.Count() });
216 ////stampa risultato
217 ////qui ho un errore perché si tenta di trasformare la group by dell'SQL (aggregazione) in un collection di collection
218 //result2.ToList().ForEach(r => Console.WriteLine(r));
219 }
220
221
222 static void InitTest()
223 {
224 UniversitaContext db = new();
225 //verifichiamo se il database esista già
226 //https://medium.com/@Usurer/ef-core-check-if-db-exists-feafe6e36f4e
227 //https://stackoverflow.com/questions/33911316/entity-framework-core-how-to-check-if-database-exists
228 if (db.Database.GetService<IRelationalDatabaseCreator>().Exists())
229 {
230 WriteLineWithColor("Il database esiste già, vuoi ricrearlo da capo? Tutti i valori precedentemente inseriti verranno persi. [Si, No]", ConsoleColor.Red);
231 bool dbErase = Console.ReadLine()?.StartsWith("si", StringComparison.CurrentCultureIgnoreCase) ?? false;
232 if (dbErase)
233 {
234 //cancelliamo il database se esiste
235 db.Database.EnsureDeleted();
236 //ricreiamo il database a partire dal model (senza dati --> tabelle vuote)
237 db.Database.EnsureCreated();
238 //inseriamo i dati nelle tabelle
239 PopulateDb();
240 Console.WriteLine("Database ricreato correttamente");
241 }
242 }
243 else //il database non esiste
244 {
245 //ricreiamo il database a partire dal model (senza dati --> tabelle vuote)
246 db.Database.EnsureCreated();
247 //popoliamo il database
248 PopulateDb();
249 Console.WriteLine("Database creato correttamente");
250 }
251
252 static void PopulateDb()
253 {
254 //1) inserisco istanze nelle tabelle che non hanno chiavi esterne -->CorsoDiLaurea, Docente
255 //creo una lista di CorsoDiLaurea e di Docente
256 List<Docente> docenti =
257 [
258 new (){CodDocente=1, Cognome="Malafronte", Nome="Gennaro",Dipartimento=Dipartimento.IngegneriaInformatica },
259 new (){CodDocente=2, Cognome="Rossi", Nome="Mario", Dipartimento=Dipartimento.Matematica},
260 new (){CodDocente=3, Cognome="Verdi", Nome="Giuseppe", Dipartimento=Dipartimento.Fisica},
261 new (){CodDocente=4, Cognome= "Smith", Nome="Albert", Dipartimento=Dipartimento.Economia}
262 ];
263 List<CorsoLaurea> corsiDiLaurea =
264 [
265 new (){CorsoLaureaId = 1,TipoLaurea=TipoLaurea.Magistrale, Facoltà=Facoltà.Ingegneria},
266 new (){CorsoLaureaId = 2,TipoLaurea=TipoLaurea.Triennale, Facoltà=Facoltà.MatematicaFisicaScienze},
267 new (){CorsoLaureaId = 3,TipoLaurea=TipoLaurea.Magistrale, Facoltà=Facoltà.Economia},
268 ];
269 using (var db = new UniversitaContext())
270 {
271 docenti.ForEach(d => db.Add(d));
272 corsiDiLaurea.ForEach(cl => db.Add(cl));
273 db.SaveChanges();
274 }
275 //2) inserisco altre istanze: Inserisco istanze di Corso e di Studente
276 List<Corso> corsi =
277 [
278 new (){CodiceCorso=1,Nome="Fondamenti di Informatica 1", CodDocente=1},
279 new (){CodiceCorso=2,Nome="Analisi Matematica 1", CodDocente=2},
280 new (){CodiceCorso=3,Nome="Fisica 1", CodDocente=3},
281 new (){CodiceCorso=4, Nome="Microeconomia 1", CodDocente=4}
282 ];
283 List<Studente> studenti =
284 [
285 new (){Matricola=1, Nome="Giovanni", Cognome="Casiraghi", CorsoLaureaId=1, AnnoNascita=2000},
286 new (){Matricola=2, Nome="Alberto", Cognome="Angela", CorsoLaureaId=2, AnnoNascita=1999},
287 new (){Matricola=3, Nome="Piero", Cognome="Gallo", CorsoLaureaId=3, AnnoNascita=2000}
288 ];
289 using (var db = new UniversitaContext())
290 {
291 corsi.ForEach(c => db.Add(c));
292 studenti.ForEach(s => db.Add(s));
293 db.SaveChanges();
294 }
295 //4) inserisco le frequenze - è la tabella molti a molti
296 List<Frequenta> frequenze =
297 [
298 new (){Matricola=1, CodCorso=1},// Giovanni Casiraghi frequenta il corso di Fondamenti di Informatica 1
299 new (){Matricola=1, CodCorso=2},// Giovanni Casiraghi frequenta il corso di Analisi Matematica 1
300 new (){Matricola=2, CodCorso=2},
301 new (){Matricola=2, CodCorso=3},
302 new (){Matricola=3, CodCorso=4}
303 ];
304 using (var db = new UniversitaContext())
305 {
306 frequenze.ForEach(f => db.Add(f));
307 db.SaveChanges();
308 }
309
310 }
311
312 }
313
314 //stampa a console con il colore di foreground selezionato e successivamente ripristina il colore precedente
315 static void WriteLineWithColor(string text, ConsoleColor consoleColor)
316 {
317 ConsoleColor previousColor = Console.ForegroundColor;
318 Console.ForegroundColor = consoleColor;
319 Console.WriteLine(text);
320 Console.ForegroundColor = previousColor;
321 }
322 static void Main(string[] args)
323 {
324
325
326 InitTest();
327 WriteLineWithColor("\nEsecuzione di Q1:" +
328 "\nStampare l'elenco degli studenti: ", ConsoleColor.Cyan);
329 PrintStudents();
330 WriteLineWithColor("\nEsecuzione di Q2:" +
331 "\nStampare l'elenco dei corsi", ConsoleColor.Cyan);
332 PrintCourses();
333 WriteLineWithColor("\nEsecuzione di Q3:" +
334 "\nModificare il docente di un corso di cui è noto l'id:", ConsoleColor.Cyan);
335 ModificaDocenteCorso(1, 1);
336 WriteLineWithColor("\nDopo la modifica i corsi sono i seguenti:", ConsoleColor.Cyan);
337 PrintCourses();
338 WriteLineWithColor("\nEsecuzione di Q4:" +
339 "\nStampare il numero di corsi seguiti dallo studente con id = 1:", ConsoleColor.Cyan);
340 PrintNumeroCorsiDiStudente(1);
341 WriteLineWithColor("\nEsecuzione di Q5:" +
342 "\nStampare il numero di corsi seguiti dallo studente con Nome=\"Giovanni\" e Cognome =\"Casiraghi\"", ConsoleColor.Cyan);
343 PrintNumeroCorsiDiStudente("Giovanni", "Casiraghi");
344 WriteLineWithColor("\nEsecuzione di Q6:" +
345 "\nStampare il numero di corsi seguiti da ogni studente", ConsoleColor.Cyan);
346 PrintNumeroCorsiFrequentatiPerStudente();
347 WriteLineWithColor("\nEsecuzione di Q7:" +
348 "\nStampare i corsi seguiti dallo studente con Nome=\"Piero\" e Cognome =\"Gallo\"", ConsoleColor.Cyan);
349 PrintCorsiDiStudente("Piero", "Gallo");
350 WriteLineWithColor("Finito!", ConsoleColor.Cyan);
351
352 }
353}
354
355