Cvičení – interakce s daty

Dokončeno

V předchozím cvičení jste vytvořili třídy entit a kontext databáze. Pak jste použili migrace EF Core k vytvoření schématu databáze.

V tomto cvičení dokončíte implementaci PizzaService . Služba používá EF Core k provádění operací CRUD s databází.

Kódování operací CRUD

Implementaci PizzaService dokončíte provedením následujících kroků v souboru Services\PizzaService.cs:

  1. Proveďte následující změny, jak je znázorněno v příkladu:

    1. Přidání direktivy using ContosoPizza.Data;
    2. Přidání direktivy using Microsoft.EntityFrameworkCore;
    3. Přidejte pole na úrovni třídy pro PizzaContext před konstruktor.
    4. Změňte signaturu metody konstruktoru PizzaContext tak, aby přijímal parametr.
    5. Změňte kód metody konstruktoru a přiřaďte k poli parametr .
    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
        /// ...
    }
    

    Volání AddSqlite metody, které jste přidali do souboru Program.cs dříve, bylo zaregistrováno PizzaContext pro injektáž závislostí. Při vytvoření PizzaService instance se do konstruktoru PizzaContext vloží .

  2. Nahraďte metodu GetAll následujícím kódem:

    public IEnumerable<Pizza> GetAll()
    {
        return _context.Pizzas
            .AsNoTracking()
            .ToList();
    }
    

    V předchozím kódu:

    • Kolekce Pizzas obsahuje všechny řádky v tabulce pizzy.
    • Rozšiřující AsNoTracking metoda dává EF Core pokyn, aby zakázal sledování změn. Vzhledem k tomu, že tato operace je jen pro čtení, AsNoTracking může optimalizovat výkon.
    • Všechny pizzy se vrátí s ToList.
  3. Nahraďte metodu GetById následujícím kódem:

    public Pizza? GetById(int id)
    {
        return _context.Pizzas
            .Include(p => p.Toppings)
            .Include(p => p.Sauce)
            .AsNoTracking()
            .SingleOrDefault(p => p.Id == id);
    }
    

    V předchozím kódu:

    • Rozšiřující Include metoda používá výraz lambda k určení, že Toppings vlastnosti navigace a Sauce mají být zahrnuty do výsledku pomocí dychtivého načítání. Bez tohoto výrazu VRÁTÍ EF Core pro tyto vlastnosti hodnotu null.
    • Metoda SingleOrDefault vrátí pizzu, která odpovídá výrazu lambda.
      • Pokud se neshoduje žádný záznam, null vrátí se hodnota .
      • Pokud se shoduje více záznamů, vyvolá se výjimka.
      • Výraz lambda popisuje záznamy, ve Id kterých se vlastnost rovná parametru id .
  4. Nahraďte metodu Create následujícím kódem:

    public Pizza Create(Pizza newPizza)
    {
        _context.Pizzas.Add(newPizza);
        _context.SaveChanges();
    
        return newPizza;
    }
    

    V předchozím kódu:

    • newPizza se předpokládá, že je platným objektem. EF Core neprovádí ověřování dat, takže jakékoli ověření musí zpracovávat modul runtime ASP.NET Core nebo uživatelský kód.
    • Metoda Add přidá entitu newPizza do grafu objektu EF Core.
    • Metoda SaveChanges dává EF Core pokyn, aby změny objektu zůstaly zachovány v databázi.
  5. Nahraďte metodu UpdateSauce následujícím kódem:

    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();
    }
    

    V předchozím kódu:

    • Odkazy na existující Pizza objekty a Sauce se vytvářejí pomocí Find. Find je optimalizovaná metoda pro dotazování záznamů podle primárního klíče. Find nejprve prohledá graf místních entit před dotazem na databázi.
    • Vlastnost Pizza.Sauce je nastavena Sauce na objekt .
    • Volání Update metody není nutné, protože EF Core zjistí, že jste nastavili Sauce vlastnost pro Pizza.
    • Metoda SaveChanges dává EF Core pokyn, aby změny objektu zůstaly zachovány v databázi.
  6. Nahraďte metodu AddTopping následujícím kódem:

    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();
    }
    

    V předchozím kódu:

    • Odkazy na existující Pizza objekty a Topping se vytvářejí pomocí Find.
    • Objekt Topping se přidá do Pizza.Toppings kolekce pomocí .Add metody . Nová kolekce se vytvoří, pokud neexistuje.
    • Metoda SaveChanges dává EF Core pokyn, aby změny objektu zůstaly zachovány v databázi.
  7. Nahraďte metodu DeleteById následujícím kódem:

    public void DeleteById(int id)
    {
        var pizzaToDelete = _context.Pizzas.Find(id);
        if (pizzaToDelete is not null)
        {
            _context.Pizzas.Remove(pizzaToDelete);
            _context.SaveChanges();
        }        
    }
    

    V předchozím kódu:

    • Metoda Find načte pizzu podle primárního klíče (v tomto případě).Id
    • Metoda Remove odebere entitu pizzaToDelete v grafu objektů EF Core.
    • Metoda SaveChanges dává EF Core pokyn, aby změny objektu zůstaly zachovány v databázi.
  8. Uložte všechny změny a spusťte dotnet buildpříkaz . Opravte všechny chyby, ke kterým dochází.

Dosadíte databázi.

Naprogramovali jste operace CRUD pro PizzaService, ale operaci čtení je jednodušší otestovat, pokud databáze obsahuje dobrá data. Rozhodnete se aplikaci upravit tak, aby se databáze při spuštění dosadila.

Upozornění

Tento kód pro dosílání databáze nebere v úvahu konflikty časování, takže při jeho používání v distribuovaném prostředí buďte opatrní, aniž byste zmírnili změny.

  1. Do složky Data přidejte nový soubor s názvem DbInitializer.cs.

  2. Do souboru Data\DbInitializer.cs přidejte následující kód:

    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();
            }
        }
    }
    

    V předchozím kódu:

    • Třída DbInitializer i Initialize metoda jsou definovány jako static.
    • InitializePizzaContext přijímá objekt jako parametr.
    • Pokud v žádné ze tří tabulek nejsou žádné záznamy, Pizzavytvoří se objekty , Saucea Topping .
    • Objekty Pizza (a jejich Sauce a Topping navigační vlastnosti) se přidají do grafu objektů pomocí AddRangepříkazu .
    • Změny grafu objektu se potvrdí do databáze pomocí SaveChangespříkazu .

Třída DbInitializer je připravená k dosadí databázi, ale je potřeba ji volat z souboru Program.cs. Následující postup vytvoří rozšiřující metodu pro IHost volání DbInitializer.Initializemetody :

  1. Do složky Data přidejte nový soubor s názvem Extensions.cs.

  2. Do souboru Data\Extensions.cs přidejte následující kód:

    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);
                }
            }
        }
    }
    

    V předchozím kódu:

    • Metoda CreateDbIfNotExists je definována jako rozšíření .IHost

    • Vytvoří se odkaz na PizzaContext službu.

    • EnsureCreated zajišťuje, že databáze existuje.

      Důležité

      Pokud databáze neexistuje, EnsureCreated vytvoří novou databázi. Nová databáze není nakonfigurovaná pro migrace, proto tuto metodu používejte opatrně.

    • Volá se DbIntializer.Initialize metoda . Objekt je PizzaContext předán jako parametr.

  3. Nakonec v souboru Program.cs nahraďte // Add the CreateDbIfNotExists method call komentář následujícím kódem pro volání nové rozšiřující metody:

    app.CreateDbIfNotExists();
    

    Tento kód volá rozšiřující metodu, kterou jste definovali dříve při každém spuštění aplikace.

  4. Uložte všechny změny a spusťte dotnet buildpříkaz .

Napsali jste veškerý kód, který potřebujete k základním operacím CRUD, a dosadili jste databázi při spuštění. V dalším cvičení tyto operace otestujete v aplikaci.

Kontrola znalostí

1.

Předpokládejme, že chcete napsat dotaz jen pro čtení. Jak dáte EF Core na vědomí, že sledování změn grafu objektu není nutné?