Exercice - Interagir avec des données
Dans l’exercice précédent, vous avez créé des classes d’entité et un contexte de base de données. Vous avez ensuite utilisé les migrations EF Core pour créer le schéma de base de données.
Dans cet exercice, vous allez mener à bien l’implémentation de PizzaService
. Le service utilise EF Core pour effectuer des opérations CRUD sur la base de données.
Coder les opérations CRUD
Pour terminer l’implémentation de PizzaService
, effectuez les étapes suivantes dans Services\PizzaService.cs :
Apportez les modifications suivantes, comme indiqué dans l’exemple :
- Ajoutez une directive
using ContosoPizza.Data;
. - Ajoutez une directive
using Microsoft.EntityFrameworkCore;
. - Ajoutez un champ au niveau de la classe pour
PizzaContext
avant le constructeur. - Modifiez la signature de la méthode de constructeur pour accepter un paramètre
PizzaContext
. - Modifiez le code de la méthode de constructeur pour attribuer le paramètre au champ.
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 /// ... }
L’appel de la méthode
AddSqlite
que vous avez ajouté à Program.cs précédemment a inscritPizzaContext
pour l’injection de dépendances. Quand l’instance dePizzaService
est créée, unPizzaContext
est injecté dans le constructeur.- Ajoutez une directive
Remplacez la méthode
GetAll
par le code suivant :public IEnumerable<Pizza> GetAll() { return _context.Pizzas .AsNoTracking() .ToList(); }
Dans le code précédent :
- La collection
Pizzas
contient toutes les lignes de la table des pizzas. - La méthode d’extension
AsNoTracking
indique à EF Core de désactiver le suivi des modifications. Étant donné que cette opération est en lecture seule,AsNoTracking
peut optimiser les performances. - Toutes les pizzas sont retournées avec
ToList
.
- La collection
Remplacez la méthode
GetById
par le code suivant :public Pizza? GetById(int id) { return _context.Pizzas .Include(p => p.Toppings) .Include(p => p.Sauce) .AsNoTracking() .SingleOrDefault(p => p.Id == id); }
Dans le code précédent :
- La méthode d’extension
Include
accepte une expression lambda pour spécifier que les propriétés de navigationToppings
etSauce
doivent être incluses dans le résultat en utilisant le chargement hâtif. Sans cette expression, EF Core retournenull
pour ces propriétés. - La méthode
SingleOrDefault
retourne une pizza qui correspond à l’expression lambda.- Si aucun enregistrement ne correspond,
null
est retourné. - Si plusieurs enregistrements correspondent, une exception est levée.
- L’expression lambda décrit les enregistrements pour lesquels la propriété
Id
est égale au paramètreid
.
- Si aucun enregistrement ne correspond,
- La méthode d’extension
Remplacez la méthode
Create
par le code suivant :public Pizza Create(Pizza newPizza) { _context.Pizzas.Add(newPizza); _context.SaveChanges(); return newPizza; }
Dans le code précédent :
newPizza
est supposé être un objet valide. EF Core n’assure pas la validation des données, de sorte que le moteur d’exécution ASP.NET Core ou le code utilisateur doit se charger de la validation.- La méthode
Add
ajoute l’entiténewPizza
au graphe d’objets EF Core. - La méthode
SaveChanges
indique à EF Core de conserver les modifications apportées aux objets dans la base de données.
Remplacez la méthode
AddTopping
par le code suivant :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(); }
Dans le code précédent :
- Les références aux objets
Pizza
etTopping
existants sont créées avecFind
. - L’objet
Topping
est ajouté à la collectionPizza.Toppings
avec la méthode.Add
. Une nouvelle collection est créée si elle n’existe pas. - La méthode
SaveChanges
indique à EF Core de conserver les modifications apportées aux objets dans la base de données.
- Les références aux objets
Remplacez la méthode
UpdateSauce
par le code suivant :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(); }
Dans le code précédent :
- Les références aux objets
Pizza
etSauce
existants sont créées avecFind
.Find
est une méthode optimisée pour interroger des enregistrements en fonction de leur clé primaire.Find
recherche d’abord dans le graphe d’entité local avant d’interroger la base de données. - La propriété
Pizza.Sauce
est définie sur l'objetSauce
. - Un appel de méthode
Update
n’est pas nécessaire, car EF Core détecte que vous définissez la propriétéSauce
surPizza
. - La méthode
SaveChanges
indique à EF Core de conserver les modifications apportées aux objets dans la base de données.
- Les références aux objets
Remplacez la méthode
DeleteById
par le code suivant :public void DeleteById(int id) { var pizzaToDelete = _context.Pizzas.Find(id); if (pizzaToDelete is not null) { _context.Pizzas.Remove(pizzaToDelete); _context.SaveChanges(); } }
Dans le code précédent :
- La méthode
Find
récupère une pizza par la clé primaire (dans ce cas,Id
). - La méthode
Remove
supprime l'entitépizzaToDelete
du graphe d’objets de EF Core. - La méthode
SaveChanges
indique à EF Core de conserver les modifications apportées aux objets dans la base de données.
- La méthode
Enregistrez toutes vos modifications et exécutez
dotnet build
. Corrigez les erreurs qui se produisent.
Amorcer la base de données
Vous avez codé les opérations CRUD pour PizzaService
, mais il est plus facile de tester l’opération Read si la base de données contient de bonnes données. Vous décidez de modifier l’application pour amorcer la base de données au démarrage.
Avertissement
Ce code d’amorçage de base de données ne tient pas compte des conditions de concurrence. Soyez donc prudent quand vous l’utilisez dans un environnement distribué sans atténuer les modifications.
Dans le dossier Données, ajoutez un nouveau fichier nommé DbInitializer.cs.
Ajoutez le code suivant à 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(); } } }
Dans le code précédent :
- La classe
DbInitializer
et la méthodeInitialize
sont toutes deux définies commestatic
. Initialize
accepte un objetPizzaContext
comme paramètre.- S’il n’y a aucun enregistrement dans les trois tables, les objets
Pizza
,Sauce
etTopping
sont créés. - Les objets
Pizza
(et leurs propriétés de navigationSauce
etTopping
) sont ajoutés au graphe d’objets avecAddRange
. - Les modifications apportées au graphe d’objets sont validées dans la base de données avec
SaveChanges
.
- La classe
La classe DbInitializer
est prête à amorcer la base de données, mais elle doit être appelée à partir de Program.cs. Les étapes suivantes créent une méthode d’extension pour IHost
qui appelle DbInitializer.Initialize
:
Dans le dossier Données, ajoutez un nouveau fichier nommé Extension.cs.
Ajoutez le code suivant à 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); } } } }
Dans le code précédent :
La méthode
CreateDbIfNotExists
est définie en tant qu’extension deIHost
.Une référence au service
PizzaContext
est créée.EnsureCreated garantit que la base de données existe.
Important
Si une base de données n’existe pas,
EnsureCreated
en crée une. La nouvelle base de données n’est pas configurée pour les migrations. Vous devez donc l’utiliser avec précaution.La méthode
DbIntializer.Initialize
est appelée. L’objetPizzaContext
est passé en tant que paramètre.
Enfin, dans Program.cs, remplacez le commentaire
// Add the CreateDbIfNotExists method call
par le code suivant pour appeler la nouvelle méthode d’extension :app.CreateDbIfNotExists();
Ce code appelle la méthode d’extension que vous avez définie précédemment chaque fois que l’application s’exécute.
Enregistrez toutes vos modifications et exécutez
dotnet build
.
Vous avez écrit tout le code dont vous avez besoin pour effectuer des opérations CRUD de base et amorcer la base de données au démarrage. Dans l’exercice suivant, vous allez tester ces opérations dans l’application.