Упражнение. Взаимодействие с данными

Завершено

В предыдущем упражнении вы создали классы сущностей и контекст базы данных. Затем вы использовали миграции EF Core для создания схемы базы данных.

В этом упражнении вы завершите реализацию PizzaService . Служба использует EF Core для выполнения операций CRUD в базе данных.

Код операций CRUD

Чтобы завершить реализацию PizzaService , выполните следующие действия в Services\PizzaService.cs:

  1. Внесите следующие изменения, как показано в примере:

    1. Добавьте директиву using ContosoPizza.Data;.
    2. Добавьте директиву using Microsoft.EntityFrameworkCore;.
    3. Добавьте поле уровня класса перед PizzaContext конструктором.
    4. Измените сигнатуру метода конструктора, чтобы принять параметр PizzaContext.
    5. Измените код метода конструктора, чтобы назначить параметр полю.
    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
        /// ...
    }
    

    Вызов AddSqlite метода, добавленный в Program.cs ранее зарегистрированных PizzaContext для внедрения зависимостей. PizzaService При создании PizzaContext экземпляра в конструктор внедряется.

  2. Замените метод GetAll следующим кодом:

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

    В предыдущем коде:

    • Коллекция Pizzas содержит все строки из таблицы видов пиццы.
    • Метод расширения AsNoTracking указывает EF Core отключить отслеживание изменений. Так как эта операция доступна только для чтения, AsNoTracking может оптимизировать производительность.
    • Для возврата всех объектов пиццы используется ToList.
  3. Замените метод GetById следующим кодом:

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

    В предыдущем коде:

    • Метод Include расширения принимает лямбда-выражение, чтобы указать, что Toppings Sauce свойства навигации должны быть включены в результат с помощью активной загрузки. Без этого выражения EF Core возвращается null для этих свойств.
    • Метод SingleOrDefault возвращает объект пиццы, соответствующий лямбда-выражению.
      • Если соответствующие записи отсутствуют, возвращается null.
      • Если найдено несколько соответствующих записей, создается исключение.
      • Лямбда-выражение содержит описание записей, где свойство Id эквивалентно параметру id.
  4. Замените метод Create следующим кодом:

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

    В предыдущем коде:

    • newPizza считается допустимым объектом. EF Core не выполняет проверку данных, поэтому ASP.NET core runtime или пользовательский код должен обрабатывать любую проверку.
    • Метод Add добавляет newPizza сущность в граф объектов EF Core.
    • Метод SaveChanges указывает EF Core сохранить изменения объекта в базе данных.
  5. Замените метод AddTopping следующим кодом:

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

    В предыдущем коде:

    • Ссылки на существующие Pizza и Topping объекты создаются с помощью Find.
    • Объект Topping добавляется в коллекцию Pizza.Toppings с .Add помощью метода. Если новая коллекция не существует, то она будет создана.
    • Метод SaveChanges указывает EF Core сохранить изменения объекта в базе данных.
  6. Замените метод UpdateSauce следующим кодом:

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

    В предыдущем коде:

    • Ссылки на существующие Pizza и Sauce объекты создаются с помощью Find. Find — это оптимизированный метод для запроса записей по первичному ключу. Find сначала выполняет поиск графа локальных сущностей перед запросом к базе данных.
    • Для свойства Pizza.Sauce указывается объект Sauce.
    • Вызов Update метода не требуется, так как EF Core обнаруживает, что для свойства задано Sauce значение Pizza.
    • Метод SaveChanges указывает EF Core сохранить изменения объекта в базе данных.
  7. Замените метод DeleteById следующим кодом:

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

    В предыдущем коде:

    • Метод Find извлекает пиццу первичным ключом (в Id данном случае).
    • Метод Remove удаляет сущность pizzaToDelete в графе объектов EF Core.
    • Метод SaveChanges указывает EF Core сохранить изменения объекта в базе данных.
  8. Сохраните все изменения и запустите dotnet build. Исправьте все ошибки, возникающие.

Заполнение базы данных

Вы закодировали операции CRUD, PizzaServiceно проще протестировать операцию чтения , если база данных содержит хорошие данные. Вы решили изменить приложение, чтобы заполнить базу данных при запуске.

Предупреждение

Этот код заполнения базы данных не учитывает условия гонки, поэтому будьте осторожны при использовании в распределенной среде без устранения изменений.

  1. В папку Data добавьте новый файл с именем DbInitializer.cs.

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

    В предыдущем коде:

    • Класс DbInitializer и метод Initialize определены как static.
    • InitializePizzaContext принимает объект в качестве параметра.
    • Если во всех трех таблицах записи отсутствуют, то создаются объекты Pizza, Sauce и Topping.
    • Объекты Pizza (и их Sauce Topping свойства навигации) добавляются в граф объектов с помощью AddRange.
    • Изменения графа объектов фиксируются в базе данных с помощью SaveChanges.

Класс DbInitializer готов заполнить базу данных, но ее необходимо вызвать из Program.cs. Ниже описано, как создать метод расширения для IHost вызовов DbInitializer.Initialize:

  1. В папку Data добавьте новый файл с именем Extensions.cs.

  2. В 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);
                }
            }
        }
    }
    

    В предыдущем коде:

    • Метод CreateDbIfNotExists определяется как расширение IHost.

    • Создается ссылка на службу PizzaContext.

    • Убедитесь, что база данных существует.

      Внимание

      Если база данных не существует, EnsureCreated создайте новую базу данных. Новая база данных не настроена для миграций, поэтому используйте этот метод с осторожностью.

    • вызывается метод DbIntializer.Initialize . Объект PizzaContext передается в качестве параметра.

  3. Наконец, в Program.cs замените // Add the CreateDbIfNotExists method call комментарий следующим кодом, чтобы вызвать новый метод расширения:

    app.CreateDbIfNotExists();
    

    Этот код вызывает метод расширения, определенный ранее при каждом запуске приложения.

  4. Сохраните все изменения и запустите dotnet build.

Вы написали весь код, который необходимо выполнить базовые операции CRUD и загрузить базу данных при запуске. В следующем упражнении вы протестируете эти операции в приложении.

Проверьте свои знания

1.

Предположим, вы хотите написать запрос только для чтения. Как указать в EF Core, что не нужно отслеживать изменения графа объектов?