<script>
  import VscUsingMySqlConnector from "../../assets/img/49_vsc_using_mysqlconnector.png";
</script>

<svelte:head>
  <title>Conectando com um banco de dados</title>
</svelte:head>

<section>
  <h1>Conectando com um banco de dados</h1>

  <nav>
    <a href="/">Início</a>
    <span>|</span>
    <a href="https://git.gbrl.dev/Gabriel/Biblioteca/archive/etapa3-v1.zip">Código fonte</a>
  </nav>

  <p>
    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 <em>persistidos</em> (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 <a href="https://mariadb.org/" target="_blank">MariaDB</a>, 
    um fork open-source do MySQL criado e mantido pelos seus desenvolvedores originais.
  </p>

  <p>
    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 <a href="https://mariadb.com/kb/en/other-net-connectors/" target="_blank">documentação do MariaDB</a> recomenda a utilização da
    biblioteca <a href="https://mysqlconnector.net/" target="_blank">MySqlConnector</a>, portanto, iremos instalá-la no nosso projeto.
  </p>

  <h2>MySqlConnector</h2>

  <p>
    Abra o terminal no seu projeto, e execute o comando:
  </p>

<pre><code>{`cd Biblioteca
dotnet add package MySqlConnector`}</code></pre>

  <p>
    Note que o comando precisa ser executado <em>no diretório onde existe o arquivo <code>Biblioteca.csproj</code></em>. Para confirmar se a biblioteca foi adicionada corretamente,
    adicione a seguinte linha no topo do seu <code>Program.cs</code>.
  </p>

<pre><code>{`using MySqlConnector;`}</code></pre>

  <p>
    Se o Visual Studio Code não mostra nenhum erro, então a instalação foi bem sucedida.
  </p>

  <div>
    <img src="{VscUsingMySqlConnector}" alt="">
  </div>

  <h2>Modelo de dados</h2>

  <p>
    O banco de dados que vamos utilizar possui uma tabela com o nome <code>Livro</code>. Esta tabela possui os seguintes campos:
  </p>

<pre><code>{`Id           BIGINT       PRIMARY KEY NOT NULL AUTO_INCREMENT,
Isbn         VARCHAR(255) 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,`}</code></pre>

  <h2>Classe de modelo e repositório</h2>

  <p>
    Vamos criar duas novas classes no projeto: uma classe <code>Livro</code> que irá espelhar a tabela no banco de dados, e uma classe <code>LivroRepository</code> que irá possuir
    métodos para a consulta e manipulação dos registros nessa tabela. Mas antes disso, vamos limpar o <code>Program.cs</code>, 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.
  </p>

  <p>
    Apague todo o conteúdo do seu <code>Program.cs</code> e deixe-o exatamente assim:
  </p>

<pre><code>{`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/{id}", () =>
{
    
});

// Obtém os dados de um livro individual.
app.MapGet("/livros/{id}", () =>
{
    
});

// Remove um livro.
app.MapDelete("/livros/{id}", () =>
{
    
});

app.Run();`}</code></pre>

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

<pre><code>{`namespace Biblioteca.Models;

public class Livro
{
    public long Id { get; set; }
    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; }
}`}</code></pre>

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

<pre><code>{`namespace Biblioteca.Repositories;

public class LivroRepository
{

}`}</code></pre>

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

  <pre><code>{`namespace Biblioteca.Repositories;

public class LivroRepository
{
    private const string ConnString = "Server=gbrl.dev;Port=5306;User ID=sistemasdistribuidos.aluno;Password=eW03avS7M8kOUL1A9bZWW2RTIfzEI1Di;Database=sistemasdistribuidos";
}`}</code></pre>

  <h2>Listagem de livros</h2>

  <p>
    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.
  </p>

<pre><code>{`public async Task<IEnumerable<Livro>> Obter(int pagina)
{

}`}</code></pre>

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

  <p>
    Um método <em>público</em> é 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 <code>Program.cs</code>.
  </p>

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

  <p>
    Um método <em>assíncrono</em> é um método que pode ser executado <em>assíncronamente</em>, 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.
  </p>

  <p>
    O parâmetro <code>int pagina</code> 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.
  </p>

  <p>
    Agora, vamos implementar o método, que ficará assim:
  </p>

<pre><code>{`public async Task<IEnumerable<Livro>> 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<Livro>();

    cmd.CommandText = "SELECT Id, 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()
        {
            Id           = reader.GetInt64(0),
            Isbn         = reader.GetString(1),
            Titulo       = reader.GetString(2),
            Autor        = reader.GetString(3),
            Genero       = reader.GetString(4),
            Descricao    = reader.GetString(5),
            Foto         = reader.GetString(6),
            Keywords     = reader.GetString(7),
            Ativo        = reader.GetBoolean(8),
            CriadoEm     = reader.GetDateTime(9),
            AtualizadoEm = reader.GetDateTime(10),
        });
    }

    return lista;
}`}</code></pre>

  <p>
    Vamos analisar cada trecho do método.
  </p>

<pre><code>{`using var conn = new MySqlConnection(ConnString);
using var cmd  = conn.CreateCommand();

await conn.OpenAsync();`}</code></pre>

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

  <p>
    Com <code>await conn.OpenAsync()</code> fazemos com que a conexão com o banco de dados seja aberta.
  </p>

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

  <p>
    O <a href="https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/" target="_blank">modelo de programação assíncrona</a> é uma característica que 
    está presente em quase toda a linguagem, mas não precisamos nos aprofundar nela por enquanto. Basta saber que devemos utilizar <code>await</code> em métodos assíncronos, e
    que se o utilizarmos, o nosso método deve ser também assíncrono, sendo declarado com <code>async Task</code>.
  </p>

<pre><code>{`var take   = 30;
var offset = take * Math.Max(pagina-1, 0);
var lista  = new List<Livro>();`}</code></pre>

  <p>
    Em seguida criamos três variáveis. <code>take</code> é o número de livros que queremos obter por página.
    <code>offset</code> é 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 <math>n</math> primeiros livros. Já se quisermos a segunda página,
    vamos pular <math>n</math> livros e depois obter os <math>n</math> próximos.
    Generalizando, para obter a página <math>p</math> (considerando que as páginas começam do <math>1</math>), com <math>n</math> elementos, precisamos pular <math>n(p-1)</math> livros.
  </p>

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

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

<pre><code>{`cmd.CommandText = "SELECT Id, 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);`}</code></pre>

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

<pre><code>{`using var reader = await cmd.ExecuteReaderAsync();`}</code></pre>

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

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

  <p>
    Este laço é repetido enquanto <code>await reader.ReadAsync()</code> retornar <code>true</code>, 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 <code>reader</code>, como <code>reader.GetString(0)</code>. O número passado como
    parâmetro para estas funções indica a posição da coluna lida. Por exemplo, na consulta que realizamos (<code>SELECT Id, Isbn, Titulo, ...</code>), a coluna <code>Id</code> é a primeira,
    portanto, para ler este valor, devemos executar <code>reader.GetInt64(0)</code> (<code>Int64</code>, um número inteiro de 64-bits, é um sinônimo para <code>long</code>).
    <code>Isbn</code> é a segunda, então o seu valor é obtido através de <code>reader.GetString(1)</code>, e assim
    sucessivamente.
  </p>

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

<pre><code>{`return lista;`}</code></pre>

<p>
  Para testarmos este método, vamos chamá-lo no endpoint de listagem em <code>Program.cs</code>
</p>

<pre><code>{`// 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;
});`}</code></pre>

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

<pre><code>{`app.MapGet("/livros", async () => ...`}</code></pre>

  <p>
    Agora execute este endpoint. Se tudo deu certo, você verá alguns livros cadastrados no retorno.
  </p>

  <h2>Obtendo um livro pela chave primária</h2>

  <p>
    O Id é a chave primária da tabela. Vamos implementar um método que consulta um único livro a partir deste dado.
  </p>

<pre><code>{`public async Task<Livro> Obter(long id)
{
    using var conn = new MySqlConnection(ConnString);
    using var cmd  = conn.CreateCommand();

    await conn.OpenAsync();

    cmd.CommandText = "SELECT Id, Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Id=@id";
    cmd.Parameters.AddWithValue("id", id);

    using var reader = await cmd.ExecuteReaderAsync();

    var existe = await reader.ReadAsync();

    if (!existe)
    {
        throw new Exception($"Livro {id} não encontrado");
    }

    return new()
    {
        Id           = reader.GetInt64(0),
        Isbn         = reader.GetString(1),
        Titulo       = reader.GetString(2),
        Autor        = reader.GetString(3),
        Genero       = reader.GetString(4),
        Descricao    = reader.GetString(5),
        Foto         = reader.GetString(6),
        Keywords     = reader.GetString(7),
        Ativo        = reader.GetBoolean(8),
        CriadoEm     = reader.GetDateTime(9),
        AtualizadoEm = reader.GetDateTime(10),
    };
}`}</code></pre>

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

<pre><code>{`cmd.CommandText = "SELECT Id, Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Id=@id";
cmd.Parameters.AddWithValue("id", id);`}</code></pre>

  <p>
    A consulta é montada na variável <code>cmd</code>, o parâmetro <code>id</code> é adicionado ao comando.
  </p>

<pre><code>{`using var reader = await cmd.ExecuteReaderAsync();

var existe = await reader.ReadAsync();

if (!existe)
{
    throw new Exception($"Livro {id} não encontrado");
}

return new()
{
    Id           = reader.GetInt64(0),
    Isbn         = reader.GetString(1),
    Titulo       = reader.GetString(2),
    Autor        = reader.GetString(3),
    Genero       = reader.GetString(4),
    Descricao    = reader.GetString(5),
    Foto         = reader.GetString(6),
    Keywords     = reader.GetString(7),
    Ativo        = reader.GetBoolean(8),
    CriadoEm     = reader.GetDateTime(9),
    AtualizadoEm = reader.GetDateTime(10),
};`}</code></pre>

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

  <p>
    Vamos chamar este novo método ao endpoint de consulta no <code>Program.cs</code>.
  </p>

<pre><code>{`// Obtém os dados de um livro individual.
app.MapGet("/livros/{id}", async (long id) =>
{
    var repo = new LivroRepository();
    var res  = await repo.Obter(id);

    return res;
});`}</code></pre>

  <h2>Criando livros</h2>

  <p>
    Vamos implementar agora o método de criação de livros:
  </p>

<pre><code>{`public async Task<Livro> Criar(Livro dados)
{

}`}</code></pre>

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

<pre><code>{`public async Task<Livro> 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.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();

    using var cmd2 = conn.CreateCommand();

    cmd2.CommandText = "SELECT LAST_INSERT_ID()";

    using var reader = await cmd2.ExecuteReaderAsync();

    await reader.ReadAsync();

    livro.Id = reader.GetInt64(0);

    return livro;
}`}</code></pre>

  <p>
    O método se inicia com a abertura da conexão com o banco de dados.
  </p>

<pre><code>{`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,
};`}</code></pre>

  <p>
    Criamos uma variável que será uma <em>cópia</em> 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.
  </p>

  <p>
    O símbolo <code>?</code> é o operador de <em>propagação de nulos</em>. 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, <code>dados.Descricao</code> é um valor do tipo <code>string</code>, que pode assumir um valor nulo. Na expressão <code>dados.Descricao.Trim()</code>, se <code>dados.Descricao</code> for nulo, um
    <code>NullReferenceException</code> será lançado, pois não é possível executar um método ou acessar uma propriedade em um objeto nulo. Já na expressão <code>dados.Descricao?.Trim()</code>, se <code>dados.Descricao</code>
    for nulo, como há o operador de propagação de nulos, <code>.Trim()</code> não será executado, e o resultado da expressão será <code>null</code>.
  </p>

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

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

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

<pre><code>{`if (livro.Titulo == "")
{
    throw new Exception("O título do livro é obrigatório.");
}`}</code></pre>

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

<pre><code>{`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()`}</code></pre>

  <p>
    Assim como no método de listagem e da consulta por chave primária, montamos o SQL com um INSERT, adicionamos os parâmetros do comando, com os
    dados do objeto <code>livro</code>, e depois o executamos.
  </p>

<pre><code>{`using var cmd2 = conn.CreateCommand();

cmd2.CommandText = "SELECT LAST_INSERT_ID()";

using var reader = await cmd2.ExecuteReaderAsync();

await reader.ReadAsync();

livro.Id = reader.GetInt64(0);

return livro;`}</code></pre>

  <p>
    Em seguida, executamos uma consulta para obter o id do livro recém criado, e o atribuímos à <code>livro.Id</code>, para
    que possamos incluir este dado, a chave primária e identificador do livro criado, na resposta do endpoint.
  </p>

  <p>
    Adicionando-o ao endpoint de criação, temos:
  </p>

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

    return res;
});`}</code></pre>

  <h2>Editando um livro</h2>

  <p>
    O método de edição é similar ao de criação.
  </p>

<pre><code>{`public async Task<Livro> Editar(long id, Livro dados)
{
    using var conn = new MySqlConnection(ConnString);
    using var cmd  = conn.CreateCommand();

    await conn.OpenAsync();

    var livro = await Obter(id);

    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 Id=@id
        ";

    cmd.Parameters.AddWithValue("id",           id);
    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;
}`}</code></pre>

  <p>
    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.
  </p>

<pre><code>{`var livro = await Obter(id);

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;`}</code></pre>

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

<pre><code>{`cmd.CommandText =
    @"
    UPDATE Livro SET
    Titulo=@titulo, Autor=@autor, Genero=@genero, Descricao=@descricao, Foto=@foto, Keywords=@keywords, AtualizadoEm=@atualizadoem
    WHERE Id=@id
    ";

cmd.Parameters.AddWithValue("id",           id);
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;`}</code></pre>

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

  <p>
    Vamos agora chamar este método no seu endpoint.
  </p>

<pre><code>{`// Edita um livro.
app.MapPut("/livros/{id}", async (long id, Livro livro) =>
{
    var repo = new LivroRepository();
    var res  = await repo.Editar(id, livro);

    return res;
});`}</code></pre>

  <h2>Removendo um livro</h2>

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

<pre><code>{`public async Task Desativar(long id)
{
    using var conn = new MySqlConnection(ConnString);
    using var cmd  = conn.CreateCommand();

    await conn.OpenAsync();

    cmd.CommandText = "UPDATE Livro SET Ativo=false WHERE Id=@id";
    cmd.Parameters.AddWithValue("id", id);

    await cmd.ExecuteNonQueryAsync();
}`}</code></pre>

  <p>
    Vamos adicioná-lo agora ao seu endpoint.
  </p>

<pre><code>{`// Remove um livro.
app.MapDelete("/livros/{id}", async (long id) =>
{
    var repo = new LivroRepository();

    await repo.Desativar(id);
});`}</code></pre>

  <h2>Finalizando</h2>

  <p>
    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.
  </p>

  <ul>
    <li>
      O endpoint de listagem está sempre obtendo a primeira página da consulta. Tente fazer seu endpoint receber um parâmetro de url
      (como <code>/livros?pagina=1</code>), para que seja possível obter as próximas páginas além da primeira.
    </li>
    <li>
      Observe que, na listagem, os livros que foram removidos (ou seja, que estão com <code>Ativo=false</code>) ainda aparecem. Não queremos que isso aconteça, queremos
      que os livros inativos não apareçam. Como você poderia fazer isso?
    </li>
  </ul>

  <p>
    O código-fonte do projeto desenvolvido até agora pode ser <a href="https://git.gbrl.dev/Gabriel/Biblioteca/archive/etapa3-v1.zip">baixado aqui</a>. 
  </p>

</section>