Exercício – Interagir com dados

Concluído

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:

  1. Efetue 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 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 adicionou PizzaContext a Program.cs anteriormente registada para injeção de dependências. Quando a PizzaService instância é criada, é injetada uma PizzaContext 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 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.
  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 de extensão utiliza uma expressão lambda para especificar que as Toppings propriedades de navegação e Sauce 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 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 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 a newPizza 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.
  5. 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 e Sauce são criadas com Find. 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 o Sauce objeto.
    • Uma Update chamada de método é desnecessária porque o EF Core deteta que definiu a Sauce propriedade em Pizza.
    • O SaveChanges método instrui o EF Core a manter as alterações do objeto à base de dados.
  6. 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 e Topping são criadas com Find.
    • 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.
  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 obtém 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 manter as alterações do objeto à base de dados.
  8. 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.

  1. Na pasta Dados , adicione um novo ficheiro com o nome 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 DbInitializer classe e Initialize o método são definidos como static.
    • Initialize aceita um PizzaContext objeto como um parâmetro.
    • Se não existirem registos em nenhuma das três tabelas, Pizza, Saucee Topping os objetos são criados.
    • Os Pizza objetos (e as respetivas Sauce propriedades e Topping de navegação) são adicionados ao gráfico de objetos com AddRange.
    • As alterações ao gráfico de objetos são consolidadas na base de dados com SaveChanges.

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:

  1. Na pasta Dados , adicione um novo ficheiro com o nome 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 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. O PizzaContext objeto é transmitido como um parâmetro.

  3. 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.

  4. 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.

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?