Exercício - Interaja com dados

Concluído

No exercício anterior, você criou classes de entidade e um contexto de banco de dados. Em seguida, você usou migrações do EF Core para criar o esquema de banco de dados.

Neste exercício, você conclui a PizzaService implementação. O serviço usa o EF Core para executar operações CRUD no banco de dados.

Codifique as operações CRUD

Para concluir a PizzaService implementação, conclua as seguintes etapas em Serviços\PizzaService.cs:

  1. Faça as seguintes alterações, conforme mostrado no exemplo:

    1. Adicione uma using ContosoPizza.Data; diretiva.
    2. Adicione uma using Microsoft.EntityFrameworkCore; diretiva.
    3. Adicione um campo de nível de classe para PizzaContext antes do construtor.
    4. Altere a assinatura do método do construtor para aceitar um PizzaContext parâmetro.
    5. 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 você adicionou a Program.cs registrada PizzaContext anteriormente para injeção de dependência. Quando a PizzaService instância é criada, um PizzaContext é injetado no construtor.

  2. 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 controle de alterações. Como essa operação é somente leitura, AsNoTracking pode otimizar o desempenho.
    • Todas as pizzas são devolvidas com ToList.
  3. 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 extension usa uma expressão lambda para especificar que as Toppings propriedades e Sauce navigation devem ser incluídas no resultado usando carregamento ansioso. Sem essa expressão, o EF Core retorna null para essas propriedades.
    • O SingleOrDefault método 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 registros em que a Id propriedade é igual ao id parâmetro.
  4. 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 é assumido como um objeto válido. O EF Core não faz validação de dados, portanto, o tempo de execução ou o código do usuário do ASP.NET Core deve lidar com qualquer validação.
    • O Add método adiciona a newPizza entidade ao gráfico de objeto EF Core.
    • O SaveChanges método instrui o EF Core a persistir as alterações de objeto no banco de dados.
  5. 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 e existentes Pizza Topping são criadas usando Findo .
    • O Topping objeto é adicionado à Pizza.Toppings coleção com o .Add método. Uma nova coleção é criada se ela não existir.
    • O SaveChanges método instrui o EF Core a persistir as alterações de objeto no banco de dados.
  6. 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 e existentes Pizza Sauce são criadas usando Findo . Find é um método otimizado para consultar registros por sua chave primária. Find pesquisa primeiro o gráfico de entidade local antes de consultar o banco de dados.
    • A Pizza.Sauce propriedade é definida como o Sauce objeto.
    • Uma Update chamada de método é desnecessária porque o EF Core deteta que você definiu a Sauce propriedade em Pizza.
    • O SaveChanges método instrui o EF Core a persistir as alterações de objeto no banco de dados.
  7. 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 recupera uma pizza pela chave primária (que é Id neste caso).
    • O Remove método remove a pizzaToDelete entidade no gráfico de objetos do EF Core.
    • O SaveChanges método instrui o EF Core a persistir as alterações de objeto no banco de dados.
  8. Salve todas as suas alterações e execute dotnet build. Corrija quaisquer erros que ocorram.

Semear o banco de dados

Você codificou as operações CRUD para PizzaService, mas é mais fácil testar a operação Read se o banco de dados contiver bons dados. Você decide modificar o aplicativo para semear o banco de dados na inicialização.

Aviso

Esse código de propagação de banco de dados não leva em conta as condições de corrida, portanto, tenha cuidado ao usá-lo em um ambiente distribuído sem atenuar as alterações.

  1. Na pasta Dados, adicione um novo arquivo chamado DbInitializer.cs.

  2. 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 classe e Initialize o DbInitializer método são definidos como static.
    • Initialize aceita um PizzaContext objeto como parâmetro.
    • Se não houver registros em nenhuma das três tabelas, Pizza, Saucee Topping objetos serão criados.
    • Os Pizza objetos (e suas Sauce propriedades de Topping navegação) são adicionados ao gráfico de objetos usando AddRange.
    • As alterações no gráfico de objetos são confirmadas no banco de dados usando SaveChanges.

A DbInitializer classe está pronta para semear o banco de dados, mas precisa ser chamada a partir de Program.cs. As etapas a seguir criam um método de extensão para IHost que chama DbInitializer.Initialize:

  1. Na pasta Dados, adicione um novo arquivo chamado Extensions.cs.

  2. 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 de IHost.

    • É criada uma referência ao PizzaContext serviço.

    • EnsureCreated garante que o banco de dados exista.

      Importante

      Se um banco de dados não existir, EnsureCreated criará um novo banco de dados. O novo banco de dados não está configurado para migrações, portanto, use esse método com cuidado.

    • O DbIntializer.Initialize método é chamado. O PizzaContext objeto é passado como um parâmetro.

  3. Finalmente, no Program.cs, substitua o // Add the CreateDbIfNotExists method call comentário com o seguinte código para chamar o novo método de extensão:

    app.CreateDbIfNotExists();
    

    Esse código chama o método de extensão que você definiu anteriormente cada vez que o aplicativo é executado.

  4. Salve todas as suas 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ê testa essas operações no aplicativo.

Verifique o seu conhecimento

1.

Suponha que quer escrever uma consulta só de leitura. Como indica ao EF Core que o controlo de alterações do gráfico de objeto é desnecessário?