Exercice - Interagir avec des données

Effectué

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 :

  1. Apportez les modifications suivantes, comme indiqué dans l’exemple :

    1. Ajoutez une directive using ContosoPizza.Data;.
    2. Ajoutez une directive using Microsoft.EntityFrameworkCore;.
    3. Ajoutez un champ au niveau de la classe pour PizzaContext avant le constructeur.
    4. Modifiez la signature de la méthode de constructeur pour accepter un paramètre PizzaContext.
    5. 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 inscrit PizzaContext pour l’injection de dépendances. Quand l’instance de PizzaService est créée, un PizzaContext est injecté dans le constructeur.

  2. 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.
  3. 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 navigation Toppings et Sauce doivent être incluses dans le résultat en utilisant le chargement hâtif. Sans cette expression, EF Core retourne Null 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ètre id.
  4. 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’effectue pas de validation des données, toute validation doit être gérée par le runtime ASP.NET Core ou le code utilisateur.
    • 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.
  5. 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 et Sauce existants sont créées avec Find. 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'objet Sauce.
    • Un appel de méthode Update n’est pas nécessaire, car EF Core détecte que vous définissez la propriété Sauce sur Pizza.
    • La méthode SaveChanges indique à EF Core de conserver les modifications apportées aux objets dans la base de données.
  6. 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 et Topping existants sont créées avec Find.
    • L’objet Topping est ajouté à la collection Pizza.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.
  7. 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.
  8. 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 de lecture 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.

  1. Dans le dossier Données, ajoutez un nouveau fichier nommé DbInitializer.cs.

  2. 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éthode Initialize sont toutes deux définies comme static.
    • Initialize accepte un objet PizzaContext comme paramètre.
    • S’il n’y a aucun enregistrement dans les trois tables, les objets Pizza, Sauce et Topping sont créés.
    • Les objets Pizza (et leurs propriétés de navigation Sauce et Topping) sont ajoutés au graphe d’objets avec AddRange.
    • Les modifications apportées au graphe d’objets sont validées dans la base de données avec SaveChanges.

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 :

  1. Dans le dossier Données, ajoutez un nouveau fichier nommé Extension.cs.

  2. 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 de IHost.

    • 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’objet PizzaContext est passé en tant que paramètre.

  3. 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.

  4. 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.

Vérifiez vos connaissances

1.

Supposons que vous voulez écrire une requête en lecture seule. Comment indiquez-vous à EF Core que le suivi des modifications du graphe des objets n’est pas nécessaire ?