Esercizio - Interagire con i dati
Nell'esercizio precedente, sono state create classi di entità e un contesto di database. Si sono usate le migrazioni di EF Core per creare lo schema del database.
In questo esercizio viene completata l'implementazione PizzaService
. Il servizio usa EF Core per eseguire operazioni CRUD nel database.
Codice delle operazioni CRUD
Per terminare l'implementazione PizzaService
, completare i seguenti passaggi in Services\PizzaService.cs:
Apportare le seguenti modifiche come mostrato nell'esempio:
- Aggiungere una direttiva
using ContosoPizza.Data;
. - Aggiungere una direttiva
using Microsoft.EntityFrameworkCore;
. - Aggiungere un campo a livello di classe per
PizzaContext
prima del costruttore. - Modificare la firma del metodo del costruttore in modo che accetti un parametro
PizzaContext
. - Modificare il codice del metodo del costruttore in modo da assegnare il parametro al campo.
using ContosoPizza.Models; using ContosoPizza.Data; using Microsoft.EntityFrameworkCore; namespace ContosoPizza.Services; public class PizzaService { private readonly PizzaContext _context; public PizzaService(PizzaContext context) { _context = context; } /// ... /// CRUD operations removed for brevity /// ... }
La chiamata al metodo
AddSqlite
aggiunta in precedenza a Program.cs ha registratoPizzaContext
per l'inserimento delle dipendenza. Quando l'istanzaPizzaService
viene creata,PizzaContext
viene inserito nel costruttore.- Aggiungere una direttiva
Sostituire il metodo
GetAll
con il codice seguente:public IEnumerable<Pizza> GetAll() { return _context.Pizzas .AsNoTracking() .ToList(); }
Nel codice precedente:
- La raccolta
Pizzas
contiene tutte le righe della tabella pizzas. - Il metodo di estensione
AsNoTracking
indica a EF Core di disabilitare il rilevamento delle modifiche. Poiché questa operazione è di sola lettura,AsNoTracking
può ottimizzare le prestazioni. - Tutte le pizze vengono restituite con
ToList
.
- La raccolta
Sostituire il metodo
GetById
con il codice seguente:public Pizza? GetById(int id) { return _context.Pizzas .Include(p => p.Toppings) .Include(p => p.Sauce) .AsNoTracking() .SingleOrDefault(p => p.Id == id); }
Nel codice precedente:
- Il metodo di estensione
Include
accetta un'espressione lambda per specificare che le proprietà di navigazioneToppings
eSauce
devono essere incluse nel risultato usando il caricamento eager. Senza questa espressione, EF Core restituiscenull
per tali proprietà. - Il metodo
SingleOrDefault
restituisce una pizza corrispondente all'espressione lambda.- Se non viene trovato alcun record corrispondente, viene restituito
null
. - Se vengono trovati più record corrispondenti, viene generata un'eccezione.
- L'espressione lambda descrive i record in cui la proprietà
Id
è uguale al parametroid
.
- Se non viene trovato alcun record corrispondente, viene restituito
- Il metodo di estensione
Sostituire il metodo
Create
con il codice seguente:public Pizza Create(Pizza newPizza) { _context.Pizzas.Add(newPizza); _context.SaveChanges(); return newPizza; }
Nel codice precedente:
- Si presuppone che
newPizza
sia un oggetto valido. EF Core non esegue la convalida dei dati, quindi il runtime di ASP.NET Core o il codice utente deve gestire le eventuali convalide. - Il metodo
Add
aggiunge l'entitànewPizza
al grafico di oggetti di EF Core. - Il metodo
SaveChanges
indica a EF Core di rendere permanenti le modifiche apportate all'oggetto nel database.
- Si presuppone che
Sostituire il metodo
AddTopping
con il codice seguente:public void AddTopping(int pizzaId, int toppingId) { var pizzaToUpdate = _context.Pizzas.Find(pizzaId); var toppingToAdd = _context.Toppings.Find(toppingId); if (pizzaToUpdate is null || toppingToAdd is null) { throw new InvalidOperationException("Pizza or topping does not exist"); } if(pizzaToUpdate.Toppings is null) { pizzaToUpdate.Toppings = new List<Topping>(); } pizzaToUpdate.Toppings.Add(toppingToAdd); _context.SaveChanges(); }
Nel codice precedente:
- I riferimenti agli oggetti
Pizza
eTopping
esistenti vengono creati utilizzandoFind
. - L'oggetto
Topping
viene aggiunto all'insiemePizza.Toppings
con il metodo.Add
. Se non esiste, viene creata una nuova raccolta. - Il metodo
SaveChanges
indica a EF Core di rendere permanenti le modifiche apportate all'oggetto nel database.
- I riferimenti agli oggetti
Sostituire il metodo
UpdateSauce
con il codice seguente:public void UpdateSauce(int pizzaId, int sauceId) { var pizzaToUpdate = _context.Pizzas.Find(pizzaId); var sauceToUpdate = _context.Sauces.Find(sauceId); if (pizzaToUpdate is null || sauceToUpdate is null) { throw new InvalidOperationException("Pizza or sauce does not exist"); } pizzaToUpdate.Sauce = sauceToUpdate; _context.SaveChanges(); }
Nel codice precedente:
- I riferimenti agli oggetti
Pizza
eSauce
esistenti vengono creati utilizzandoFind
.Find
è un metodo ottimizzato per eseguire query sui record in base alla chiave primaria.Find
esegue una ricerca nel grafico delle entità locale prima di eseguire query sul database. - La proprietà
Pizza.Sauce
è impostata sull'oggettoSauce
. - La chiamata al metodo
Update
non è necessaria perché EF Core rileva che la proprietàSauce
è stata impostata suPizza
. - Il metodo
SaveChanges
indica a EF Core di rendere permanenti le modifiche apportate all'oggetto nel database.
- I riferimenti agli oggetti
Sostituire il metodo
DeleteById
con il codice seguente:public void DeleteById(int id) { var pizzaToDelete = _context.Pizzas.Find(id); if (pizzaToDelete is not null) { _context.Pizzas.Remove(pizzaToDelete); _context.SaveChanges(); } }
Nel codice precedente:
- Il metodo
Find
recupera una pizza in base alla chiave primaria, che in questo caso èId
. - Il metodo
Remove
rimuove l'entitàpizzaToDelete
nel grafico di oggetti di EF Core. - Il metodo
SaveChanges
indica a EF Core di rendere permanenti le modifiche apportate all'oggetto nel database.
- Il metodo
Salvare tutte le modifiche ed eseguire
dotnet build
. Correggere eventuali errori che si verificano.
Specificare il valore di inizializzazione del database
Sono state codificate le operazioni CRUD per PizzaService
, ma è più facile testare l'operazione di lettura se il database contiene dati validi. A questo punto l'app verrà modificata per effettuare il seeding del database all'avvio.
Avviso
Il codice di seeding del database non tiene conto delle race condition, quindi è necessario prestare attenzione quando viene usato in un ambiente distribuito senza mitigare le modifiche.
Nella cartella Data aggiungere un nuovo file denominato DbInitializer.cs.
Aggiungere il codice seguente a Data\DbInitializer.cs:
using ContosoPizza.Models; namespace ContosoPizza.Data { public static class DbInitializer { public static void Initialize(PizzaContext context) { if (context.Pizzas.Any() && context.Toppings.Any() && context.Sauces.Any()) { return; // DB has been seeded } var pepperoniTopping = new Topping { Name = "Pepperoni", Calories = 130 }; var sausageTopping = new Topping { Name = "Sausage", Calories = 100 }; var hamTopping = new Topping { Name = "Ham", Calories = 70 }; var chickenTopping = new Topping { Name = "Chicken", Calories = 50 }; var pineappleTopping = new Topping { Name = "Pineapple", Calories = 75 }; var tomatoSauce = new Sauce { Name = "Tomato", IsVegan = true }; var alfredoSauce = new Sauce { Name = "Alfredo", IsVegan = false }; var pizzas = new Pizza[] { new Pizza { Name = "Meat Lovers", Sauce = tomatoSauce, Toppings = new List<Topping> { pepperoniTopping, sausageTopping, hamTopping, chickenTopping } }, new Pizza { Name = "Hawaiian", Sauce = tomatoSauce, Toppings = new List<Topping> { pineappleTopping, hamTopping } }, new Pizza { Name="Alfredo Chicken", Sauce = alfredoSauce, Toppings = new List<Topping> { chickenTopping } } }; context.Pizzas.AddRange(pizzas); context.SaveChanges(); } } }
Nel codice precedente:
- La classe
DbInitializer
e il metodoInitialize
sono entrambi definiti comestatic
. Initialize
accetta un oggettoPizzaContext
come parametro.- Se non sono presenti record in una delle tre tabelle, vengono creati gli oggetti
Pizza
,Sauce
eTopping
. - Gli oggetti
Pizza
(e le relative proprietà di esplorazioneSauce
eTopping
) vengono aggiunti al grafico di oggetti usandoAddRange
. - Le modifiche apportate al grafico di oggetti vengono sottoposte a commit nel database usando
SaveChanges
.
- La classe
La classe DbInitializer
è pronta per eseguire il seeding del database, ma deve essere chiamata da Program.cs. I passaggi seguenti creano un metodo di estensione per IHost
che chiama DbInitializer.Initialize
:
Nella cartella Data aggiungere un nuovo file denominato Extensions.cs.
Aggiungere il codice seguente a Data\Extensions.cs:
namespace ContosoPizza.Data; public static class Extensions { public static void CreateDbIfNotExists(this IHost host) { { using (var scope = host.Services.CreateScope()) { var services = scope.ServiceProvider; var context = services.GetRequiredService<PizzaContext>(); context.Database.EnsureCreated(); DbInitializer.Initialize(context); } } } }
Nel codice precedente:
Il metodo
CreateDbIfNotExists
viene definito come estensione diIHost
.Viene creato un riferimento al servizio
PizzaContext
.EnsureCreated assicura l’esistenza del database.
Importante
Se un database non esiste,
EnsureCreated
crea un nuovo database. Il nuovo database non è configurato per le migrazioni, quindi è consigliabile usare questo metodo con cautela.Viene chiamato il metodo
DbIntializer.Initialize
. L'oggettoPizzaContext
viene passato come parametro.
Infine, in Program.cs, sostituire il commento
// Add the CreateDbIfNotExists method call
con il seguente codice per chiamare il nuovo metodo di estensione:app.CreateDbIfNotExists();
Questo codice chiama il metodo di estensione definito in precedenza ogni volta che l'app viene eseguita.
Salvare tutte le modifiche ed eseguire
dotnet build
.
È stato scritto tutto il codice necessario per eseguire operazioni CRUD di base ed effettuare il seeding del database all'avvio. Nell'esercizio successivo, queste operazioni verranno testate nell’app.