Exercício: interagir com os dados
No exercício anterior, você criou classes de entidade e um contexto de banco de dados. Em seguida, você usou as migrações do EF Core para criar o esquema do banco de dados.
Neste exercício, você concluirá a implementação de PizzaService
. O serviço usa o EF Core para executar operações CRUD no banco de dados.
Codificar as operações CRUD
Para concluir a implementação de PizzaService
, conclua as seguintes etapas em Services\PizzaService.cs:
Faça as seguintes alterações, conforme mostrado no exemplo:
- Adicione uma diretiva
using ContosoPizza.Data;
. - Adicione uma diretiva
using Microsoft.EntityFrameworkCore;
. - Adicione um campo de nível de classe para
PizzaContext
antes do construtor. - Altere a assinatura do método do construtor para aceitar um parâmetro
PizzaContext
. - Altere o código do método do construtor para atribuir o parâmetro ao campo.
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 /// ... }
A chamada de método
AddSqlite
adicionada anteriormente a Program.cs registrouPizzaContext
para a injeção de dependência. Quando a instânciaPizzaService
é criada, umPizzaContext
é injetado como uma dependência.- Adicione uma diretiva
Substitua o método
GetAll
pelo seguinte código:public IEnumerable<Pizza> GetAll() { return _context.Pizzas .AsNoTracking() .ToList(); }
No código anterior:
- A coleção
Pizzas
contém todas as linhas na tabela de pizzas. - O método de extensão
AsNoTracking
instrui o EF Core a desabilitar o controle de alterações. Como essa operação é somente leitura, oAsNoTracking
pode otimizar o desempenho. - Todas as pizzas são retornadas com
ToList
.
- A coleção
Substitua o método
GetById
pelo seguinte código:public Pizza? GetById(int id) { return _context.Pizzas .Include(p => p.Toppings) .Include(p => p.Sauce) .AsNoTracking() .SingleOrDefault(p => p.Id == id); }
No código anterior:
- O método de extensão
Include
utiliza uma expressão lambda para especificar que as propriedades de navegaçãoToppings
eSauce
devem ser incluídas no resultado por meio do carregamento adiantado. Sem essa expressão, o EF Core retornanull
para essas propriedades. - O método
SingleOrDefault
retorna uma pizza que corresponde à expressão lambda.- Se nenhum registro corresponder,
null
será retornado. - Se vários registros corresponderem, uma exceção será lançada.
- A expressão lambda descreve os registros em que a propriedade
Id
é igual ao parâmetroid
.
- Se nenhum registro corresponder,
- O método de extensão
Substitua o método
Create
pelo seguinte código:public Pizza Create(Pizza newPizza) { _context.Pizzas.Add(newPizza); _context.SaveChanges(); return newPizza; }
No código anterior:
newPizza
é considerado um objeto válido. O EF Core não faz validação de dados, então o runtime do ASP.NET Core ou o código do usuário deve lidar com qualquer validação.- O método
Add
adiciona a entidadenewPizza
ao grafo de objetos do EF Core. - O método
SaveChanges
instrui o EF Core para persistir as alterações de objeto no banco de dados.
Substitua o método
AddTopping
pelo seguinte código: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(); }
No código anterior:
- Referências a objetos
Pizza
eTopping
existentes são criadas usandoFind
. - O objeto
Topping
é adicionado à coleçãoPizza.Toppings
com o método.Add
. Uma nova coleção será criada se ela não existir. - O método
SaveChanges
instrui o EF Core para persistir as alterações de objeto no banco de dados.
- Referências a objetos
Substitua o método
UpdateSauce
pelo seguinte código: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(); }
No código anterior:
- Referências a objetos
Pizza
eSauce
existentes são criadas usandoFind
.Find
é um método otimizado para consultar registros por sua chave primária.Find
pesquisa o grafo de entidade local primeiro antes de consultar o banco de dados. - A propriedade
Pizza.Sauce
é definida como o objetoSauce
. - Uma chamada de método
Update
é desnecessária porque o EF Core detecta que definimos a propriedadeSauce
emPizza
. - O método
SaveChanges
instrui o EF Core para persistir as alterações de objeto no banco de dados.
- Referências a objetos
Substitua o método
DeleteById
pelo seguinte código:public void DeleteById(int id) { var pizzaToDelete = _context.Pizzas.Find(id); if (pizzaToDelete is not null) { _context.Pizzas.Remove(pizzaToDelete); _context.SaveChanges(); } }
No código anterior:
- O método
Find
recupera uma pizza pela chave primária (nesse caso,Id
). - O método
Remove
remove a entidadepizzaToDelete
no grafo de objeto do EF Core. - O método
SaveChanges
instrui o EF Core para persistir as alterações de objeto no banco de dados.
- O método
Salve todas as alterações e execute
dotnet build
. Corrija todos os erros que ocorrerem.
Propagar o banco de dados
Você codificou as operações CRUD para PizzaService
, mas é mais fácil testar a operação de Leitura se o banco de dados contiver bons dados. Você decide modificar o aplicativo para propagar o banco de dados na inicialização.
Aviso
Esse código de propagação de banco de dados não explica as condições de corrida, portanto, tenha cuidado ao usá-lo em um ambiente distribuído sem atenuar as alterações.
Na pasta Dados, adicione um novo arquivo chamado DbInitializer.cs.
Adicione o seguinte código em 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(); } } }
No código anterior:
- A classe
DbInitializer
e o métodoInitialize
são definidos comostatic
. Initialize
aceita um objetoPizzaContext
como um parâmetro.- Se não houver nenhum registro em nenhuma das três tabelas, os objetos
Pizza
,Sauce
eTopping
serão criados. - Os objetos
Pizza
(e as respectivas propriedades de navegaçãoSauce
eTopping
) são adicionados ao grafo do objeto usandoAddRange
. - As alterações do grafo de objeto são confirmadas no banco de dados usando
SaveChanges
.
- A classe
A classe DbInitializer
está pronta para propagar o banco de dados, mas precisa ser chamada por Program.cs. As seguintes etapas criam um método de extensão para IHost
que chama DbInitializer.Initialize
:
Na pasta Dados, adicione um novo arquivo chamado Extensions.cs.
Adicione o seguinte código a 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); } } } }
No código anterior:
O método
CreateDbIfNotExists
é definido como uma extensão deIHost
.Uma referência ao serviço
PizzaContext
é criada.EnsureCreated garante que o banco de dados exista.
Importante
EnsureCreated
criará um banco de dados se ele ainda não existir. O novo banco de dados não está configurado para migrações, portanto, use-o com cuidado.O método
DbIntializer.Initialize
é chamado. O objetoPizzaContext
é passado como um parâmetro.
Por fim, em Program.cs, substitua o comentário
// Add the CreateDbIfNotExists method call
pelo seguinte código a fim de chamar o novo método de extensão:app.CreateDbIfNotExists();
Esse código chama, sempre que o aplicativo é executado, o método de extensão que você definiu anteriormente.
Salve todas as alterações e execute
dotnet build
.
Você escreveu todo o código necessário para fazer operações CRUD básicas e semear o banco de dados na inicialização. No próximo exercício, você testará essas operações no aplicativo.