Esercizi

Esercitazioni guidate sulla creazione di database e sull'esecuzione di query da programma C#, mediante LINQ

Introduzione

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.

Database Romanzi

  Ottieni il codice

  1. 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
    
  2. Creare la cartella Model con dentro le classi del modello dei dati

  3. 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);  
    
  4. 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
    
  5. 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.

Query sul database Romanzi

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:

Struttura Progetto Romanzi

Si scriva nel file Program.cs il codice relativo a:

  • Popolamento del database
  • Gestione dell’input
  • Esecuzione delle query richieste dalla traccia

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

Database DBUtilizziPC

  Ottieni il codice

  1. 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:

    • Gli Id siano di tipo intero ad auto-incremento
    • Nome, 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
    
  2. Creare la cartella Model con dentro le classi del modello dei dati

  3. 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);  
    
  4. Effettuare la migration del database e la creazione fisica del file di database di SQLite:

    1dotnet ef migrations add InitialCreate
    2dotnet ef database update
    
  5. Creare un metodo static void PopulateDB() nella classe Program che inserisce:

  • Almeno 6 classi, con le relative aule
  • Almeno 10 studenti distribuiti su più classi (non necessariamente devono esserci studenti di ogni classe)
  • Almeno 20 computer
  • Almeno una quindicina di utilizzi

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}
File 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}
File 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}
File 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}

Query sul database DBUtilizziPC

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 4IA
Q2(): Stampa a Console il numero di alunni per ogni classe
Q3(): 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}

Database Università

  Ottieni il codice

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:

  • Facoltà è un enumerativo che contiene i valori: 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
File 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
File 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
File 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}
File 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
File 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

Query sul database Università

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