Exercício – Interagir com dados
No exercício anterior, criou classes de entidades e um contexto de base de dados. Em seguida, utilizou migrações do EF Core para criar o esquema da base de dados.
Neste exercício, irá concluir a PizzaService
implementação. O serviço utiliza o EF Core para realizar operações CRUD na base de dados.
Codificar as operações CRUD
Para concluir a PizzaService
implementação, conclua os seguintes passos em Services\PizzaService.cs:
Efetue as seguintes alterações, conforme mostrado no exemplo:
- Adicione uma
using ContosoPizza.Data;
diretiva. - Adicione uma
using Microsoft.EntityFrameworkCore;
diretiva. - Adicione um campo de nível de classe para
PizzaContext
antes do construtor. - Altere a assinatura do método construtor para aceitar um
PizzaContext
parâmetro. - 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
AddSqlite
chamada de método que adicionouPizzaContext
a Program.cs anteriormente registada para injeção de dependências. Quando aPizzaService
instância é criada, é injetada umaPizzaContext
no construtor.- Adicione uma
Substitua o método
GetAll
pelo código abaixo:public IEnumerable<Pizza> GetAll() { return _context.Pizzas .AsNoTracking() .ToList(); }
No código anterior:
- A
Pizzas
coleção contém todas as linhas na tabela de pizzas. - O
AsNoTracking
método de extensão instrui o EF Core a desativar o controlo de alterações. Uma vez que esta operação é só de leitura,AsNoTracking
pode otimizar o desempenho. - Todas as pizzas são devolvidas com
ToList
.
- A
Substitua o método
GetById
pelo código abaixo: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
Include
método de extensão utiliza uma expressão lambda para especificar que asToppings
propriedades de navegação eSauce
devem ser incluídas no resultado através do carregamento ansioso. Sem esta expressão, o EF Core devolve nulo para essas propriedades. - O
SingleOrDefault
método devolve uma pizza que corresponde à expressão lambda.- Se não houver registos correspondentes,
null
será devolvido. - Se vários registos corresponderem, é emitida uma exceção.
- A expressão lambda descreve os registos em que a
Id
propriedade é igual aoid
parâmetro.
- Se não houver registos correspondentes,
- O
Substitua o método
Create
pelo código abaixo:public Pizza Create(Pizza newPizza) { _context.Pizzas.Add(newPizza); _context.SaveChanges(); return newPizza; }
No código anterior:
newPizza
assume-se que é um objeto válido. O EF Core não faz a validação de dados, pelo que qualquer validação tem de ser processada pelo ASP.NET Core runtime ou código de utilizador.- O
Add
método adiciona anewPizza
entidade ao grafo de objeto EF Core. - O
SaveChanges
método instrui o EF Core a manter as alterações do objeto à base de dados.
Substitua o método
UpdateSauce
pelo código abaixo: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:
- As referências a objetos existentes
Pizza
eSauce
são criadas comFind
.Find
é um método otimizado para consultar registos pela chave primária.Find
procura primeiro o gráfico de entidade local antes de consultar a base de dados. - A
Pizza.Sauce
propriedade está definida como oSauce
objeto. - Uma
Update
chamada de método é desnecessária porque o EF Core deteta que definiu aSauce
propriedade emPizza
. - O
SaveChanges
método instrui o EF Core a manter as alterações do objeto à base de dados.
- As referências a objetos existentes
Substitua o método
AddTopping
pelo código abaixo: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:
- As referências a objetos existentes
Pizza
eTopping
são criadas comFind
. - O
Topping
objeto é adicionado àPizza.Toppings
coleção com o.Add
método . Uma nova coleção é criada se não existir. - O
SaveChanges
método instrui o EF Core a manter as alterações do objeto à base de dados.
- As referências a objetos existentes
Substitua o método
DeleteById
pelo código abaixo: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
Find
método obtém uma pizza pela chave primária (que éId
neste caso). - O
Remove
método remove apizzaToDelete
entidade no gráfico de objetos do EF Core. - O
SaveChanges
método instrui o EF Core a manter as alterações do objeto à base de dados.
- O
Guarde todas as alterações e execute
dotnet build
. Corrigir quaisquer erros que ocorram.
Propagar a base de dados
Codizou as operações CRUD para PizzaService
, mas é mais fácil testar a operação de leitura se a base de dados contiver bons dados. Decide modificar a aplicação para propagar a base de dados no arranque.
Aviso
Este código de propagação da base de dados não tem em conta as condições de corrida, por isso tenha cuidado ao utilizá-lo num ambiente distribuído sem mitigar as alterações.
Na pasta Dados , adicione um novo ficheiro com o nome DbInitializer.cs.
Adicione o seguinte código a 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
DbInitializer
classe eInitialize
o método são definidos comostatic
. Initialize
aceita umPizzaContext
objeto como um parâmetro.- Se não existirem registos em nenhuma das três tabelas,
Pizza
,Sauce
eTopping
os objetos são criados. - Os
Pizza
objetos (e as respetivasSauce
propriedades eTopping
de navegação) são adicionados ao gráfico de objetos comAddRange
. - As alterações ao gráfico de objetos são consolidadas na base de dados com
SaveChanges
.
- A
A DbInitializer
classe está pronta para propagar a base de dados, mas tem de ser chamada a partir de Program.cs. Os passos seguintes criam um método de extensão para IHost
as chamadas DbInitializer.Initialize
:
Na pasta Dados , adicione um novo ficheiro com o nome 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
CreateDbIfNotExists
método é definido como uma extensão deIHost
.É criada uma referência ao
PizzaContext
serviço.EnsureCreated garante que a base de dados existe.
Importante
Se uma base de dados não existir,
EnsureCreated
cria uma nova base de dados. A nova base de dados não está configurada para migrações, por isso utilize este método com cuidado.O
DbIntializer.Initialize
método é chamado. OPizzaContext
objeto é transmitido como um parâmetro.
Por fim, em Program.cs, substitua o
// Add the CreateDbIfNotExists method call
comentário pelo seguinte código para chamar o novo método de extensão:app.CreateDbIfNotExists();
Este código chama o método de extensão que definiu anteriormente sempre que a aplicação é executada.
Guarde todas as alterações e execute
dotnet build
.
Escreveu todo o código necessário para realizar operações CRUD básicas e propagar a base de dados no arranque. No próximo exercício, irá testar essas operações na aplicação.