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

Завершено

В предыдущем упражнении вы создали классы сущностей и контекст базы данных. Затем вы использовали миграции 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 или кодом пользователя.
    • Метод Add добавляет сущность в newPizza граф объектов EF Core.
    • Метод SaveChanges указывает EF Core сохранить изменения объекта в базе данных.
  5. Замените метод 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 обнаруживает, что свойство задано в PizzaSauce .
    • Метод SaveChanges указывает EF Core сохранить изменения объекта в базе данных.
  6. Замените метод 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 сохранить изменения объекта в базе данных.
  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.
    • Initialize принимает объект в PizzaContext качестве параметра.
    • Если во всех трех таблицах записи отсутствуют, то создаются объекты 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 гарантирует, что база данных существует.

      Важно!

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

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

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

    app.CreateDbIfNotExists();
    

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

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

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

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

1.

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