Ćwiczenie — interakcja z danymi

Ukończone

W poprzednim ćwiczeniu utworzono klasy jednostek i kontekst bazy danych. Następnie użyto migracji platformy EF Core do utworzenia schematu bazy danych.

W tym ćwiczeniu ukończysz implementację PizzaService . Usługa używa programu EF Core do wykonywania operacji CRUD w bazie danych.

Kod operacji CRUD

Aby zakończyć implementację PizzaService , wykonaj następujące kroki w obszarze Usługi\PizzaService.cs:

  1. Wprowadź następujące zmiany, jak pokazano w przykładzie:

    1. Dodaj dyrektywę using ContosoPizza.Data; .
    2. Dodaj dyrektywę using Microsoft.EntityFrameworkCore; .
    3. Dodaj pole na poziomie klasy przed PizzaContext konstruktorem.
    4. Zmień sygnaturę PizzaContext metody konstruktora, aby zaakceptować parametr.
    5. Zmień kod metody konstruktora, aby przypisać parametr do pola.
    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
        /// ...
    }
    

    Wywołanie AddSqlite metody dodane do Program.cs wcześniej zarejestrowane PizzaContext w celu wstrzyknięcia zależności. Po utworzeniu PizzaService PizzaContext wystąpienia element jest wstrzykiwany do konstruktora.

  2. Zastąp metodę GetAll poniższym kodem:

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

    Powyższy kod:

    • Kolekcja Pizzas zawiera wszystkie wiersze w tabeli pizzas.
    • Metoda AsNoTracking rozszerzenia instruuje program EF Core, aby wyłączyć śledzenie zmian. Ponieważ ta operacja jest tylko do odczytu, AsNoTracking może zoptymalizować wydajność.
    • Wszystkie pizze są zwracane z ToList.
  3. Zastąp metodę GetById poniższym kodem:

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

    Powyższy kod:

    • Metoda Include rozszerzenia przyjmuje wyrażenie lambda, aby określić, że Toppings właściwości nawigacji i Sauce mają być uwzględnione w wyniku przy użyciu chętnego ładowania. Bez tego wyrażenia program EF Core zwraca null dla tych właściwości.
    • Metoda SingleOrDefault zwraca pizzę zgodną z wyrażeniem lambda.
      • Jeśli rekordy nie są zgodne, null zwracana jest wartość .
      • W przypadku dopasowania wielu rekordów zgłaszany jest wyjątek.
      • Wyrażenie lambda opisuje rekordy, w których Id właściwość jest równa parametrowi id .
  4. Zastąp metodę Create poniższym kodem:

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

    Powyższy kod:

    • newPizza przyjmuje się, że jest prawidłowym obiektem. Program EF Core nie wykonuje walidacji danych, więc środowisko uruchomieniowe ASP.NET Core lub kod użytkownika muszą obsługiwać każdą walidację.
    • Metoda Add dodaje newPizza jednostkę do grafu obiektów programu EF Core.
    • Metoda SaveChanges instruuje program EF Core, aby utrwał zmiany obiektu w bazie danych.
  5. Zastąp metodę AddTopping poniższym kodem:

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

    Powyższy kod:

    • Odwołania do istniejących Pizza obiektów i Topping są tworzone przy użyciu polecenia Find.
    • Obiekt Topping jest dodawany do kolekcji Pizza.Toppings za pomocą .Add metody . Zostanie utworzona nowa kolekcja, jeśli nie istnieje.
    • Metoda SaveChanges instruuje program EF Core, aby utrwał zmiany obiektu w bazie danych.
  6. Zastąp metodę UpdateSauce poniższym kodem:

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

    Powyższy kod:

    • Odwołania do istniejących Pizza obiektów i Sauce są tworzone przy użyciu polecenia Find. Find jest zoptymalizowaną metodą wykonywania zapytań o rekordy według ich klucza podstawowego. Find najpierw przeszukuje wykres jednostki lokalnej przed wykonaniem zapytania do bazy danych.
    • Właściwość Pizza.Sauce jest ustawiona na Sauce obiekt .
    • Wywołanie Update metody jest niepotrzebne, ponieważ program EF Core wykrywa, że właściwość jest ustawiana Sauce na .Pizza
    • Metoda SaveChanges instruuje program EF Core, aby utrwał zmiany obiektu w bazie danych.
  7. Zastąp metodę DeleteById poniższym kodem:

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

    Powyższy kod:

    • Metoda Find pobiera pizzę przy użyciu klucza podstawowego (w tym przypadku).Id
    • Metoda Remove usuwa pizzaToDelete jednostkę na wykresie obiektów programu EF Core.
    • Metoda SaveChanges instruuje program EF Core, aby utrwał zmiany obiektu w bazie danych.
  8. Zapisz wszystkie zmiany i uruchom polecenie dotnet build. Napraw wszelkie występujące błędy.

Inicjowanie bazy danych

Wykonano kod operacji CRUD dla PizzaServiceelementu , ale łatwiej jest przetestować operację Odczyt , jeśli baza danych zawiera dobre dane. Decydujesz się zmodyfikować aplikację w celu zainicjowania bazy danych podczas uruchamiania.

Ostrzeżenie

Ten kod rozmieszczania bazy danych nie uwzględnia warunków wyścigu, dlatego należy zachować ostrożność podczas korzystania z niej w środowisku rozproszonym bez ograniczania zmian.

  1. W folderze Dane dodaj nowy plik o nazwie DbInitializer.cs.

  2. Dodaj następujący kod do pliku 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();
            }
        }
    }
    

    Powyższy kod:

    • Klasa DbInitializer i Initialize metoda są definiowane jako static.
    • InitializePizzaContext akceptuje obiekt jako parametr.
    • Jeśli nie ma żadnych rekordów w żadnej z trzech tabel, Pizzatworzone są obiekty , Saucei Topping .
    • Obiekty Pizza (i ich Sauce Topping właściwości nawigacji) są dodawane do grafu obiektów przy użyciu polecenia AddRange.
    • Zmiany grafu obiektów są zatwierdzane w bazie danych przy użyciu polecenia SaveChanges.

Klasa DbInitializer jest gotowa do inicjowania bazy danych, ale musi być wywoływana z Program.cs. Poniższe kroki tworzą metodę rozszerzenia, IHost która wywołuje DbInitializer.Initializemetodę :

  1. W folderze Dane dodaj nowy plik o nazwie Extensions.cs.

  2. Dodaj następujący kod do pliku 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);
                }
            }
        }
    }
    

    Powyższy kod:

    • Metoda CreateDbIfNotExists jest definiowana jako rozszerzenie IHostklasy .

    • Zostanie utworzone odwołanie do PizzaContext usługi.

    • Upewnij się, że utworzono gwarantuje, że baza danych istnieje.

      Ważne

      Jeśli baza danych nie istnieje, EnsureCreated tworzy nową bazę danych. Nowa baza danych nie jest skonfigurowana na potrzeby migracji, dlatego należy zachować ostrożność przy użyciu tej metody.

    • Wywoływana DbIntializer.Initialize jest metoda . Obiekt PizzaContext jest przekazywany jako parametr.

  3. Na koniec w Program.cs zastąp // Add the CreateDbIfNotExists method call komentarz następującym kodem, aby wywołać nową metodę rozszerzenia:

    app.CreateDbIfNotExists();
    

    Ten kod wywołuje metodę rozszerzenia zdefiniowaną wcześniej przy każdym uruchomieniu aplikacji.

  4. Zapisz wszystkie zmiany i uruchom polecenie dotnet build.

Napisałeś cały kod, który musisz wykonać podstawowe operacje CRUD i zainicjować bazę danych podczas uruchamiania. W następnym ćwiczeniu przetestujesz te operacje w aplikacji.

Sprawdź swoją wiedzę

1.

Załóżmy, że chcesz napisać zapytanie tylko do odczytu. Jak wskażesz technologii EF Core, że śledzenie zmian grafu obiektu nie jest konieczne?