From 02531f086176a55e269cfde6f80a15cce1b3db51 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Bueno Date: Wed, 7 May 2025 17:11:05 -0300 Subject: [PATCH] bd --- .../img/49_vsc_using_mysqlconnector.png | Bin 0 -> 27687 bytes .../src/css/code.css | 1 + .../src/routes/+page.svelte | 6 +- .../src/routes/bd/+page.svelte | 771 +++++++++++++++++- .../src/routes/endpoints_2/+page.svelte | 1 - .../src/routes/projeto_de_api/+page.svelte | 2 +- 6 files changed, 754 insertions(+), 27 deletions(-) create mode 100644 material-sistemas-distribuidos/src/assets/img/49_vsc_using_mysqlconnector.png diff --git a/material-sistemas-distribuidos/src/assets/img/49_vsc_using_mysqlconnector.png b/material-sistemas-distribuidos/src/assets/img/49_vsc_using_mysqlconnector.png new file mode 100644 index 0000000000000000000000000000000000000000..fc64d8d661d91403fc1d668baea06c3ddf8c38c2 GIT binary patch literal 27687 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 literal 0 HcmV?d00001 diff --git a/material-sistemas-distribuidos/src/css/code.css b/material-sistemas-distribuidos/src/css/code.css index be8c532..e359930 100644 --- a/material-sistemas-distribuidos/src/css/code.css +++ b/material-sistemas-distribuidos/src/css/code.css @@ -29,4 +29,5 @@ pre:has(code) { border-radius: 10px; margin-top: var(--paragraph-spacing); margin-bottom: var(--paragraph-spacing); + overflow-x: auto; } \ No newline at end of file diff --git a/material-sistemas-distribuidos/src/routes/+page.svelte b/material-sistemas-distribuidos/src/routes/+page.svelte index f582aba..a4a8812 100644 --- a/material-sistemas-distribuidos/src/routes/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/+page.svelte @@ -33,11 +33,7 @@
  • Estrutura de arquivos
  • Criando e executando endpoints
  • Definindo os endpoints da Biblioteca
  • -
  • Conectando com o banco de dados
  • -
  • Mantendo livros
  • -
  • Emprestando livros
  • -
  • Sobre o protocolo HTTP
  • -
  • A arquitetura REST
  • +
  • Conectando com o banco de dados
  • diff --git a/material-sistemas-distribuidos/src/routes/bd/+page.svelte b/material-sistemas-distribuidos/src/routes/bd/+page.svelte index 8e6dd05..1364d88 100644 --- a/material-sistemas-distribuidos/src/routes/bd/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/bd/+page.svelte @@ -1,3 +1,7 @@ + + Conectando com um banco de dados @@ -10,42 +14,769 @@

    - Os endpoints que escrevemos no capítulo anterior não fazem nada além de exibir os dados que você envia na requisição. - Para que possamos fazer com que eles persistam os dados enviados, vamos utilizar um banco de dados. - Neste capítulo, vamos criar a comunicação entre o nosso projeto e esse banco de dados. + Os endpoints que escrevemos no capítulo anterior não fazem nada além de devolver os dados exatamente como você os envia na requisição. + Para que possamos fazer com que estes dados sejam persistidos (ou seja, registrados em algum lugar onde possamos consultá-los futuramente), vamos utilizar um banco de dados. + Neste capítulo, vamos criar a comunicação entre o nosso projeto e um gerenciador de banco de dados MariaDB, + um fork open-source do MySQL criado e mantido pelos seus desenvolvedores originais.

    - Vamos utilizar um banco de dados MySQL, acessível através das credenciais: + Vamos utilizar um banco de dados previamente criado para persistir e consultar livros. + Para acessá-lo a partir do nosso projeto, precisamos de uma biblioteca. Como o MariaDB é um fork do + MySQL, os dois são normalmente intercambiáveis. Bibliotecas desenvolvidas para o MySQL quase sempre irão funcionar com o MariaDB, com exceção de algumas + funcionalidades específicas. A documentação do MariaDB recomenda a utilização da + biblioteca MySqlConnector, portanto, iremos instalá-la no nosso projeto.

    -
      -
    • Usuário: sistemasdistribuidos.aluno
    • -
    • Senha: eW03avS7M8kOUL1A9bZWW2RTIfzEI1Di
    • -
    -

    MySqlConnector

    - Para gerenciar conexões e realizar consultas, vamos utilizar a biblioteca MySqlConnector. Abra - o terminal no seu projeto, e execute o comando: + Abra o terminal no seu projeto, e execute o comando:

    {`cd Biblioteca
     dotnet add package MySqlConnector`}

    - Note que o comando precisa ser executado no diretório onde existe o arquivo Biblioteca.csproj, - por isso o comando cd Biblioteca. + Note que o comando precisa ser executado no diretório onde existe o arquivo Biblioteca.csproj. Para confirmar se a biblioteca foi adicionada corretamente, + adicione a seguinte linha no topo do seu Program.cs. +

    + +
    {`using MySqlConnector;`}
    + +

    + Se o Visual Studio Code não mostra nenhum erro, então a instalação foi bem sucedida. +

    + +
    + +
    + +

    Modelo de dados

    + +

    + O banco de dados que vamos utilizar possui uma tabela com o nome Livro. Esta tabela possui os seguintes campos: +

    + +
    {`Isbn         VARCHAR(255) PRIMARY KEY NOT NULL,
    +Titulo       VARCHAR(512) NOT NULL,
    +Autor        TEXT         NOT NULL,
    +Genero       TEXT         NOT NULL,
    +Descricao    TEXT         NOT NULL,
    +Foto         TEXT         NOT NULL,
    +Keywords     TEXT         NOT NULL,
    +Ativo        BOOLEAN      NOT NULL DEFAULT 0,
    +CriadoEm     DATETIME     NOT NULL,
    +AtualizadoEm DATETIME     NOT NULL,`}
    + +

    Classe de modelo e repositório

    + +

    + Vamos criar duas novas classes no projeto: uma classe Livro que irá espelhar a tabela no banco de dados, e uma classe LivroRepository que irá possuir + métodos para a consulta e manipulação dos registros nessa tabela. Mas antes disso, vamos limpar o Program.cs, removendo partes do código que escrevemos anteriormente apenas para testar, e que + não vamos precisar mais, deixando-o pronto para utilizarmos as novas classes que iremos criar. +

    + +

    + Apague todo o conteúdo do seu Program.cs e deixe-o exatamente assim: +

    + +
    {`var builder = WebApplication.CreateBuilder(args);
    +
    +var app = builder.Build();
    +
    +// Obtém uma lista com os livros registrados.
    +app.MapGet("/livros", () =>
    +{
    +    
    +});
    +
    +// Cria um novo livro.
    +app.MapPost("/livros", () =>
    +{
    +    
    +});
    +
    +// Edita um livro.
    +app.MapPut("/livros/{isbn}", () =>
    +{
    +    
    +});
    +
    +// Obtém os dados de um livro individual.
    +app.MapGet("/livros/{isbn}", () =>
    +{
    +    
    +});
    +
    +// Remove um livro.
    +app.MapDelete("/livros/{isbn}", () =>
    +{
    +    
    +});
    +
    +app.Run();`}
    + +

    + Tendo feito isso, crie uma pasta Biblioteca/Models e, dentro dela, crie um arquivo Livro.cs. + Dentro deste arquivo, vamos criar uma classe Livro com campos que refletem os campos na tabela do banco de dados. +

    + +
    {`namespace Biblioteca.Models;
    +
    +public class Livro
    +{
    +    public string? Isbn { get; set; }
    +    public string? Titulo { get; set; }
    +    public string? Autor { get; set; }
    +    public string? Genero { get; set; }
    +    public string? Descricao { get; set; }
    +    public string? Foto { get; set; }
    +    public string? Keywords { get; set; }
    +    public bool Ativo { get; set; }
    +    public DateTime CriadoEm { get; set; }
    +    public DateTime AtualizadoEm { get; set; }
    +}`}
    + +

    + Agora, crie uma pasta Biblioteca/Repositories e, dentro dela, crie um arquivo LivroRepository.cs, + onde existirá a classe LivroRepository. +

    + +
    {`namespace Biblioteca.Repositories;
    +
    +public class LivroRepository
    +{
    +
    +}`}
    + +

    + Nesta classe, vamos precisar de uma constante que vai conter uma string de conexão, que servirá para indicar para o MySqlConnector o endereço e as credenciais do banco de dados que vamos utilizar. +

    + +
    {`namespace Biblioteca.Repositories;
    +
    +public class LivroRepository
    +{
    +    private const string ConnString = "Server=gbrl.dev;Port=5306;User ID=sistemasdistribuidos.aluno;Password=eW03avS7M8kOUL1A9bZWW2RTIfzEI1Di;Database=sistemasdistribuidos";
    +}`}
    + +

    Listagem de livros

    + +

    + Vamos escrever nesta classe métodos para as operações de listagem, consulta, inclusão, edição e remoção de livros na base de dados. Vamos começar pela + operação de listagem. +

    + +
    {`public async Task> Obter(int pagina)
    +{
    +
    +}`}
    + +

    + Este método é público (public), assíncrono (async Task), retorna um IEnumerable<Livro>, e recebe + como parâmetro um valor inteiro que representa a página da consulta +

    + +

    + Um método público é um método que pode ser executado por qualquer outro método no projeto. O nosso método precisa ser público pois ele vai ser chamado + nos endpoints que estão no Program.cs. +

    + +

    + O valor de retorno será um IEnumerable<Livro>. Um IEnumerable é uma interface do dotnet que representa + qualquer sequência iterável de objetos. Por exemplo, um objeto List<T> ou um vetor T[] são iteráveis, + portanto podem ser retornados neste método. +

    + +

    + Um método assíncrono é um método que pode ser executado assíncronamente, de forma independente do fluxo de execução do método que o chamou. + Nós não vamos utilizar nenhuma forma de paralelismo, mas precisamos que nosso método seja assíncrono mesmo assim. +

    + +

    + O parâmetro int pagina recebido indica a página da consulta. Você provavelmente já usou algum site onde interagiu com uma listagem de um conteúdo que era feita + através de páginas, a ideia aqui é a mesma. Imagine que temos três milhões de livros na base de dados, nós não queremos que nosso método leia todos eles. + Ao invés disso, vamos retornar os livros de forma paginada, com N livros por página. +

    + +

    + Agora, vamos implementar o método, que ficará assim: +

    + +
    {`public async Task> Obter(int pagina)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    var take   = 30;
    +    var offset = take * Math.Max(pagina-1, 0);
    +    var lista  = new List();
    +
    +    cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro ORDER BY CriadoEm LIMIT @offset,@take";
    +    cmd.Parameters.AddWithValue("offset", offset);
    +    cmd.Parameters.AddWithValue("take", take);
    +
    +    using var reader = await cmd.ExecuteReaderAsync();
    +
    +    while (await reader.ReadAsync())
    +    {
    +        lista.Add(new()
    +        {
    +            Isbn         = reader.GetString(0),
    +            Titulo       = reader.GetString(1),
    +            Autor        = reader.GetString(2),
    +            Genero       = reader.GetString(3),
    +            Descricao    = reader.GetString(4),
    +            Foto         = reader.GetString(5),
    +            Keywords     = reader.GetString(6),
    +            Ativo        = reader.GetBoolean(7),
    +            CriadoEm     = reader.GetDateTime(8),
    +            AtualizadoEm = reader.GetDateTime(9),
    +        });
    +    }
    +
    +    return lista;
    +}`}
    + +

    + Vamos analisar cada trecho do método. +

    + +
    {`using var conn = new MySqlConnection(ConnString);
    +using var cmd  = conn.CreateCommand();
    +
    +await conn.OpenAsync();`}
    + +

    + As variáveis conn e cmd representam, respectivamente, a conexão com o banco de dados, e o comando que iremos executar. + A declaração de ambas é feita com using pois estes dois objetos alocam recursos que devem ser liberados ao fim da execução do método. O + using é uma construção da linguagem que garante que isso aconteça sempre. +

    + +

    + Com await conn.OpenAsync() fazemos com que a conexão com o banco de dados seja aberta. +

    + +

    + Note que conn.OpenAsync() também é um método assíncrono (pois ele retorna Task). Por isso, devemos prefixar a chamada deste método com + um await. Isto é uma indicação de que queremos aguardar a conclusão de conn.OpenAsync() antes de prosseguirmos com a execução do nosso método. + E para utilizarmos um await, o nosso método deve ser também, obrigatoriamente, assíncrono. Por isso o declaramos como async Task. +

    + +

    + O modelo de programação assíncrona é uma característica que + é praticamente ubíqua no dotnet, mas não precisamos nos aprofundar nela por enquanto. Basta saber que devemos utilizar await em métodos assíncronos, e + que se o utilizarmos, o nosso método deve ser também assíncrono, sendo declarado com async Task. +

    + +
    {`var take   = 30;
    +var offset = take * Math.Max(pagina-1, 0);
    +var lista  = new List();`}
    + +

    + Em seguida criamos três variáveis. take é o número de livros que queremos obter por página. + offset é um número que indica quantos registros pular para obter a página desejada. Por exemplo, se + desejamos a primeira página, devemos obter os n primeiros livros. Já se quisermos a segunda página, + vamos pular n livros e depois obter os n próximos. + Generalizando, para obter a página p (considerando que as páginas começam do 1), com n elementos, precisamos pular n(p-1) livros. +

    + +

    + Como int pagina é um valor inteiro que pode assumir valores negativos, fazemos Math.Max(pagina-1, 0) como uma garantia para que offset nunca tenha um valor menor do que zero. +

    + +

    + A variável lista é uma lista de objetos Livro que irá guardar todos os resultados da consulta, e depois será retornada. +

    + +
    {`cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro ORDER BY CriadoEm LIMIT @offset,@take";
    +cmd.Parameters.AddWithValue("offset", offset);
    +cmd.Parameters.AddWithValue("take", take);`}
    + +

    + A primeira linha adiciona a consulta que queremos executar à variável cmd. As duas linhas seguintes adicionam take e offset + como parâmetros da consulta. +

    + +
    {`using var reader = await cmd.ExecuteReaderAsync();`}
    + +

    + Enfim, executamos a consulta através de cmd.ExecuteReaderAsync(). Este método é assíncrono, portanto deve ser prefixado por await, e além disso também aloca + recursos que devem ser liberados com using. O retorno desta função é um objeto onde podemos ler os resultados da consulta. +

    + +
    {`while (await reader.ReadAsync())
    +{
    +    lista.Add(new()
    +    {
    +        Isbn         = reader.GetString(0),
    +        Titulo       = reader.GetString(1),
    +        Autor        = reader.GetString(2),
    +        Genero       = reader.GetString(3),
    +        Descricao    = reader.GetString(4),
    +        Foto         = reader.GetString(5),
    +        Keywords     = reader.GetString(6),
    +        Ativo        = reader.GetBoolean(7),
    +        CriadoEm     = reader.GetDateTime(8),
    +        AtualizadoEm = reader.GetDateTime(9),
    +    });
    +}`}
    + +

    + Este laço é repetido enquanto await reader.ReadAsync() retornar true, o que significa que existe um resultado da consulta que pode ser lido. + Quando isso acontece, podemos ler as colunas do resultado através dos métodos de reader, como reader.GetString(0). O número passado como + parâmetro para estas funções indica a posição da coluna lida. Por exemplo, na consulta que realizamos (SELECT Isbn, Titulo, ...), a coluna Isbn é a primeira, + portanto, para ler este valor, devemos executar reader.GetString(0). Titulo é a segunda, então o seu valor é obtido através de reader.GetString(1), e assim + sucessivamente. +

    + +

    + Realizamos a leitura de todas as colunas do resultado, e montamos um objeto do tipo Livro, o qual adicionamos à lista lista, que é o valor de retorno do método de listagem. +

    + +
    {`return lista;`}
    + +

    + Para testarmos este método, vamos chamá-lo no endpoint de listagem em Program.cs +

    + +
    {`// Obtém uma lista com os livros registrados.
    +app.MapGet("/livros", async () =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Obter(pagina: 1);
    +
    +    return res;
    +});`}
    + +

    + Como LivroRepository.Obter(int) é assíncrono, o endpoint também deve ser. Fazemos isso adicionando async na declaração da sua função. +

    + +
    {`app.MapGet("/livros", async () => ...`}
    + +

    + Agora execute este endpoint. Se tudo deu certo, você verá alguns livros cadastrados no retorno. +

    + +

    Obtendo um livro pelo ISBN

    + +

    + O ISBN é a chave primária da tabela. Vamos implementar um método que consulta um único livro a partir deste dado. +

    + +
    {`public async Task Obter(string isbn)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Isbn=@isbn";
    +    cmd.Parameters.AddWithValue("isbn", isbn);
    +
    +    using var reader = await cmd.ExecuteReaderAsync();
    +
    +    var existe = await reader.ReadAsync();
    +
    +    if (!existe)
    +    {
    +        throw new Exception($"Livro com ISBN {isbn} não encontrado");
    +    }
    +
    +    return new()
    +    {
    +        Isbn         = reader.GetString(0),
    +        Titulo       = reader.GetString(1),
    +        Autor        = reader.GetString(2),
    +        Genero       = reader.GetString(3),
    +        Descricao    = reader.GetString(4),
    +        Foto         = reader.GetString(5),
    +        Keywords     = reader.GetString(6),
    +        Ativo        = reader.GetBoolean(7),
    +        CriadoEm     = reader.GetDateTime(8),
    +        AtualizadoEm = reader.GetDateTime(9),
    +    };
    +}`}
    + +

    + As três primeiras linhas deste método, assim como no de listagem, abre uma conexão com o banco de dados. +

    + +
    {`cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Isbn=@isbn";
    +cmd.Parameters.AddWithValue("isbn", isbn);`}
    + +

    + A consulta é montada na variável cmd, o parâmetro isbn é adicionado ao comando. +

    + +
    {`using var reader = await cmd.ExecuteReaderAsync();
    +
    +var existe = await reader.ReadAsync();
    +
    +if (!existe)
    +{
    +    throw new Exception($"Livro com ISBN {isbn} não encontrado");
    +}
    +
    +return new()
    +{
    +    Isbn         = reader.GetString(0),
    +    Titulo       = reader.GetString(1),
    +    Autor        = reader.GetString(2),
    +    Genero       = reader.GetString(3),
    +    Descricao    = reader.GetString(4),
    +    Foto         = reader.GetString(5),
    +    Keywords     = reader.GetString(6),
    +    Ativo        = reader.GetBoolean(7),
    +    CriadoEm     = reader.GetDateTime(8),
    +    AtualizadoEm = reader.GetDateTime(9),
    +};`}
    + +

    + Assim como na listagem, await cmd.ExecuteReaderAsync() é chamado para ler os resultados da consulta. + Como esperamos que só haja um registro com aquele ISBN, executamos await reader.ReadAsync() apenas uma vez. + Se este método retorna false, é por que nenhum registro foi encontrado com aquele ISBN. Neste caso, lançamos um erro. + Do contrário, utilizamos os métodos de leitura do registro lido para montar um objeto Livro que será retornado. +

    + +

    + Vamos chamar este novo método ao endpoint de consulta no Program.cs. +

    + +
    {`// Obtém os dados de um livro individual.
    +app.MapGet("/livros/{isbn}", async (string isbn) =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Obter(isbn);
    +
    +    return res;
    +});`}
    + +

    Criando livros

    + +

    + Vamos implementar agora o método de criação de livros: +

    + +
    {`public async Task Criar(Livro dados)
    +{
    +
    +}`}
    + +

    + Este método recebe um objeto Livro como parâmetro, contendo os dados do novo livro que deve ser inserido no banco de dados. Vamos implementá-lo. +

    + +
    {`public async Task Criar(Livro dados)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    var livro = new Livro
    +    {
    +        Isbn         = dados.Isbn?.Trim() ?? "",
    +        Titulo       = dados.Titulo?.Trim() ?? "",
    +        Autor        = dados.Autor?.Trim() ?? "",
    +        Genero       = dados.Genero?.Trim() ?? "",
    +        Descricao    = dados.Descricao?.Trim() ?? "",
    +        Foto         = dados.Foto?.Trim() ?? "",
    +        Keywords     = dados.Keywords?.Trim() ?? "",
    +        Ativo        = true,
    +        CriadoEm     = DateTime.Now,
    +        AtualizadoEm = default,
    +    };
    +
    +    if (livro.Isbn == "")
    +    {
    +        throw new Exception("O ISBN do livro é obrigatório.");
    +    }
    +
    +    if (livro.Titulo == "")
    +    {
    +        throw new Exception("O título do livro é obrigatório.");
    +    }
    +
    +    cmd.CommandText =
    +        @"
    +        INSERT INTO Livro
    +        (Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm)
    +        VALUES
    +        (@isbn, @titulo, @autor, @genero, @descricao, @foto, @keywords, @ativo, @criadoem, @atualizadoem)
    +        ";
    +
    +    cmd.Parameters.AddWithValue("isbn",         livro.Isbn);
    +    cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +    cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +    cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +    cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +    cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +    cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +    cmd.Parameters.AddWithValue("ativo",        livro.Ativo);
    +    cmd.Parameters.AddWithValue("criadoem",     livro.CriadoEm);
    +    cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
    +
    +    await cmd.ExecuteNonQueryAsync();
    +
    +    return livro;
    +}`}
    + +

    + O método se inicia com a abertura da conexão com o banco de dados. +

    + +
    {`var livro = new Livro
    +{
    +    Isbn         = dados.Isbn?.Trim() ?? "",
    +    Titulo       = dados.Titulo?.Trim() ?? "",
    +    Autor        = dados.Autor?.Trim() ?? "",
    +    Genero       = dados.Genero?.Trim() ?? "",
    +    Descricao    = dados.Descricao?.Trim() ?? "",
    +    Foto         = dados.Foto?.Trim() ?? "",
    +    Keywords     = dados.Keywords?.Trim() ?? "",
    +    Ativo        = true,
    +    CriadoEm     = DateTime.Now,
    +    AtualizadoEm = default,
    +};`}
    + +

    + Criamos uma variável que será uma cópia do objeto recebido no parâmetro. Fazemos isso para tratar os dados do livro, formatando-os, e assegurando + que os seus campos assumam os valores corretos. Este objeto é contém os dados que serão enviados para o banco de dados. +

    + +

    + O símbolo ? é o operador de propagação de nulos. Em uma cadeia de expressões de acesso de membros em um objeto, um propagador de nulo faz com que, se um membro for nulo, o resultado de toda a expressão seja nula. + Por exemplo, dados.Descricao é um valor do tipo string, que pode assumir um valor nulo. Na expressão dados.Descricao.Trim(), se dados.Descricao for nulo, um + NullReferenceException será lançado, pois não é possível executar um método ou acessar uma propriedade em um objeto nulo. Já na expressão dados.Descricao?.Trim(), se dados.Descricao + for nulo, como há o operador de propagação de nulos, .Trim() não será executado, e o resultado da expressão será null. +

    + +

    + De forma semelhante, o ?? age sobre dois operandos. Na expressão var x = A ?? B, a variável x irá assumir o valor de A apenas se ela não for nula. Do contrário, + x assumirá o valor de B. +

    + +

    + Portanto, na expressão dados.Descricao?.Trim() ?? "", se dados.Descricao for nulo, então a expressão ficará null ?? "", que terá como resultado a string vazia "". Ou seja, + Se dados.Descricao for nulo, livro.Descricao será uma string vazia, e nunca assumirá null. +

    + +

    + Fazemos isso nos campos do objeto livro para garantir que nenhum deles seja nulo, já que no banco de dados, todos os campos da tabela são NOT NULL. +

    + +
    {`if (livro.Isbn == "")
    +{
    +    throw new Exception("O ISBN do livro é obrigatório.");
    +}
    +
    +if (livro.Titulo == "")
    +{
    +    throw new Exception("O título do livro é obrigatório.");
    +}`}
    + +

    + Fazemos algumas validações básicas, para assegurar que os dados obrigatórios do registro tenham sido preenchidos. +

    + +
    {`cmd.CommandText =
    +    @"
    +    INSERT INTO Livro
    +    (Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm)
    +    VALUES
    +    (@isbn, @titulo, @autor, @genero, @descricao, @foto, @keywords, @ativo, @criadoem, @atualizadoem)
    +    ";
    +
    +cmd.Parameters.AddWithValue("isbn",         livro.Isbn);
    +cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +cmd.Parameters.AddWithValue("ativo",        livro.Ativo);
    +cmd.Parameters.AddWithValue("criadoem",     livro.CriadoEm);
    +cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);`}
    + +

    + Assim como no método de listagem e da consulta por ISBN, montamos o SQL com um INSERT, e depois adicionamos os parâmetros do comando, com os + dados do objeto livro. +

    + +
    {`await cmd.ExecuteNonQueryAsync();
    +
    +return livro;`}
    + +

    + Em seguida, executamos o insert através de await cmd.ExecuteNonQueryAsync(). Como a operação é de escrita, não há resultado a ser lido. +

    + +

    + Adicionando-o ao endpoint de criação, temos: +

    + +
    {`// Cria um novo livro.
    +app.MapPost("/livros", async (Livro livro) =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Criar(livro);
    +
    +    return res;
    +});`}
    + +

    Editando um livro

    + +

    + O método de edição é similar ao de criação. +

    + +
    {`public async Task Editar(string isbn, Livro dados)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    var livro = await Obter(isbn);
    +
    +    livro.Titulo       = dados.Titulo?.Trim() ?? "";
    +    livro.Autor        = dados.Autor?.Trim() ?? "";
    +    livro.Genero       = dados.Genero?.Trim() ?? "";
    +    livro.Descricao    = dados.Descricao?.Trim() ?? "";
    +    livro.Foto         = dados.Foto?.Trim() ?? "";
    +    livro.Keywords     = dados.Keywords?.Trim() ?? "";
    +    livro.AtualizadoEm = DateTime.Now;
    +
    +    cmd.CommandText =
    +        @"
    +        UPDATE Livro SET
    +        Titulo=@titulo, Autor=@autor, Genero=@genero, Descricao=@descricao, Foto=@foto, Keywords=@keywords, AtualizadoEm=@atualizadoem
    +        WHERE Isbn=@isbn
    +        ";
    +
    +    cmd.Parameters.AddWithValue("isbn",         isbn);
    +    cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +    cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +    cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +    cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +    cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +    cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +    cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
    +
    +    await cmd.ExecuteNonQueryAsync();
    +
    +    return livro;
    +}`}
    + +

    + O método recebe dois parâmetros, um isbn que identifica o livro que será editado, e um objeto com os novos dados do livro. +

    + +
    {`var livro = await Obter(isbn);
    +
    +livro.Titulo       = dados.Titulo?.Trim() ?? "";
    +livro.Autor        = dados.Autor?.Trim() ?? "";
    +livro.Genero       = dados.Genero?.Trim() ?? "";
    +livro.Descricao    = dados.Descricao?.Trim() ?? "";
    +livro.Foto         = dados.Foto?.Trim() ?? "";
    +livro.Keywords     = dados.Keywords?.Trim() ?? "";
    +livro.AtualizadoEm = DateTime.Now;`}
    + +

    + Chamamos a função Obter(string) que criamos para obter do banco de dados o livro que será editado. Em seguida, atribuímos a este objeto + os valores do objeto dados, formatando-os devidamente. +

    + +
    {`cmd.CommandText =
    +    @"
    +    UPDATE Livro SET
    +    Titulo=@titulo, Autor=@autor, Genero=@genero, Descricao=@descricao, Foto=@foto, Keywords=@keywords, AtualizadoEm=@atualizadoem
    +    WHERE Isbn=@isbn
    +    ";
    +
    +cmd.Parameters.AddWithValue("isbn",         isbn);
    +cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
    +
    +await cmd.ExecuteNonQueryAsync();
    +
    +return livro;`}
    + +

    + Em seguida, montamos o comando um UPDATE, atribuímos os parâmetros, executamos a consulta e retornamos o livro que foi editado. +

    + +

    + Vamos agora chamar este método no seu endpoint. +

    + +
    {`// Edita um livro.
    +app.MapPut("/livros/{isbn}", async (string isbn, Livro livro) =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Editar(isbn, livro);
    +
    +    return res;
    +});`}
    + +

    Removendo um livro

    + +

    + Nós não iremos remover livros do banco de dados, ao invés disso, a operação de remoção irá + editar apenas o campo Ativo de um livro, alterando-o para false. + Portanto, o método de remover será basicamente uma edição. +

    + +
    {`public async Task Desativar(string isbn)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    cmd.CommandText = "UPDATE Livro SET Ativo=false WHERE Isbn=@isbn";
    +    cmd.Parameters.AddWithValue("isbn", isbn);
    +
    +    await cmd.ExecuteNonQueryAsync();
    +}`}
    + +

    + Vamos adicioná-lo agora ao seu endpoint. +

    + +
    {`// Remove um livro.
    +app.MapDelete("/livros/{isbn}", async (string isbn) =>
    +{
    +    var repo = new LivroRepository();
    +
    +    await repo.Desativar(isbn);
    +});`}
    + +

    Finalizando

    + +

    + Implementamos todas as operações básicas de manipulação dos registros, e os adicionamos aos endpoints da nossa API. Mais ainda precisamos ajustar algumas coisas.

      -
    • Limpar Program.cs
    • -
    • criar arquivo Models/Livro.cs
    • -
    • criar arquivo Repositories/LivroRepository.cs
    • -
    • criar método de inserção
    • -
    • testar método de inserção
    • -
    • criar métodos de consulta
    • -
    • testar métodos de consulta
    • +
    • + O endpoint de listagem está sempre obtendo a primeira página da consulta. Tente fazer seu endpoint receber um parâmetro de url + (como /livros?pagina=1), para que seja possível obter as próximas páginas além da primeira. +
    • +
    • + Observe que, na listagem, os livros que foram removidos (ou seja, que estão com Ativo=false) ainda aparecem. Não queremos que isso aconteça, queremos + que os livros inativos não apareçam. Como você poderia fazer isso? +
    + \ No newline at end of file diff --git a/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte b/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte index 0cd187f..bc3415b 100644 --- a/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte @@ -24,7 +24,6 @@
    • Visualizar os livros da biblioteca disponíveis no sistema.
    • Cadastrar, editar e remover livros no sistema.
    • -
    • Registrar quando um livro foi emprestado para um aluno.

    diff --git a/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte b/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte index 13a419b..1f8a111 100644 --- a/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte @@ -19,7 +19,7 @@

    • Visualizar os livros da biblioteca disponíveis no sistema.
    • Cadastrar, editar e remover livros no sistema.
    • -
    • Registrar quando um livro foi emprestado para um aluno.
    • +