Упражнение. Взаимодействие с данными
В предыдущем упражнении вы создали классы сущностей и контекст базы данных. Затем вы использовали миграции 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 runtime или пользовательский код должен обрабатывать любую проверку.- Метод
Add
добавляетnewPizza
сущность в граф объектов EF Core. - Метод
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 сохранить изменения объекта в базе данных.
- Ссылки на существующие
Замените метод
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 сохранить изменения объекта в базе данных.
- Ссылки на существующие
Замените метод
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
создайте новую базу данных. Новая база данных не настроена для миграций, поэтому используйте этот метод с осторожностью.вызывается метод
DbIntializer.Initialize
. ОбъектPizzaContext
передается в качестве параметра.
Наконец, в Program.cs замените
// Add the CreateDbIfNotExists method call
комментарий следующим кодом, чтобы вызвать новый метод расширения:app.CreateDbIfNotExists();
Этот код вызывает метод расширения, определенный ранее при каждом запуске приложения.
Сохраните все изменения и запустите
dotnet build
.
Вы написали весь код, который необходимо выполнить базовые операции CRUD и загрузить базу данных при запуске. В следующем упражнении вы протестируете эти операции в приложении.