Упражнение. Взаимодействие с данными
В предыдущем упражнении вы создали классы сущностей и контекст базы данных. Затем вы использовали миграции EF Core для создания схемы базы данных.
В этом упражнении вы завершите реализацию PizzaService
. Служба использует EF Core для выполнения операций CRUD с базой данных.
Код операций CRUD
Чтобы завершить реализацию PizzaService
, выполните следующие действия в services\PizzaService.cs:
Внесите следующие изменения, как показано в примере:
- Добавьте директиву
using ContosoPizza.Data;
. - Добавьте директиву
using Microsoft.EntityFrameworkCore;
. - Добавьте поле уровня класса для перед
PizzaContext
конструктором. - Измените сигнатуру метода конструктора, чтобы принять параметр
PizzaContext
. - Измените код метода конструктора, чтобы назначить параметр полю.
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
в конструктор внедряется объект .- Добавьте директиву
Замените метод
GetAll
следующим кодом:public IEnumerable<Pizza> GetAll() { return _context.Pizzas .AsNoTracking() .ToList(); }
В приведенном выше коде:
- Коллекция
Pizzas
содержит все строки из таблицы видов пиццы. - Метод расширения
AsNoTracking
указывает EF Core отключить отслеживание изменений. Так как эта операция доступна только для чтения,AsNoTracking
может оптимизировать производительность. - Для возврата всех объектов пиццы используется
ToList
.
- Коллекция
Замените метод
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
.
- Если соответствующие записи отсутствуют, возвращается
- Метод
Замените метод
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 сохранить изменения объекта в базе данных.
Замените метод
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 обнаруживает, что свойство задано вPizza
Sauce
. - Метод
SaveChanges
указывает EF Core сохранить изменения объекта в базе данных.
- Ссылки на существующие
Замените метод
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 сохранить изменения объекта в базе данных.
- Ссылки на существующие
Замените метод
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 сохранить изменения объекта в базе данных.
- Метод
Сохраните все изменения и выполните команду
dotnet build
. Исправьте все возникающие ошибки.
Заполнение базы данных
Вы закодировали операции CRUD для PizzaService
, но проще протестировать операцию чтения, если база данных содержит хорошие данные. Вы решили изменить приложение, чтобы заполнить базу данных при запуске.
Предупреждение
Этот код заполнения базы данных не учитывает расовые условия, поэтому будьте осторожны при использовании в распределенной среде без устранения изменений.
В папку Data добавьте новый файл с именем DbInitializer.cs.
В 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
:
В папку Data добавьте новый файл с именем Extensions.cs.
В 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
передается в качестве параметра.
Наконец, в Файле Program.cs замените
// Add the CreateDbIfNotExists method call
комментарий следующим кодом, чтобы вызвать новый метод расширения:app.CreateDbIfNotExists();
Этот код вызывает метод расширения, определенный ранее при каждом запуске приложения.
Сохраните все изменения и выполните команду
dotnet build
.
Вы написали весь код, необходимый для выполнения базовых операций CRUD и заполнения базы данных при запуске. В следующем упражнении вы протестируете эти операции в приложении.