This commit is contained in:
Gabriel Almeida Bueno 2025-05-07 17:11:05 -03:00
parent 69822c180a
commit 02531f0861
6 changed files with 754 additions and 27 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -29,4 +29,5 @@ pre:has(code) {
border-radius: 10px; border-radius: 10px;
margin-top: var(--paragraph-spacing); margin-top: var(--paragraph-spacing);
margin-bottom: var(--paragraph-spacing); margin-bottom: var(--paragraph-spacing);
overflow-x: auto;
} }

View File

@ -33,11 +33,7 @@
<li><a href="/estrutura_de_arquivos">Estrutura de arquivos</a></li> <li><a href="/estrutura_de_arquivos">Estrutura de arquivos</a></li>
<li><a href="/endpoints_1">Criando e executando endpoints</a></li> <li><a href="/endpoints_1">Criando e executando endpoints</a></li>
<li><a href="/endpoints_2">Definindo os endpoints da Biblioteca</a></li> <li><a href="/endpoints_2">Definindo os endpoints da Biblioteca</a></li>
<li>Conectando com o banco de dados</li> <li><a href="/bd">Conectando com o banco de dados</a></li>
<li>Mantendo livros</li>
<li>Emprestando livros</li>
<li>Sobre o protocolo HTTP</li>
<li>A arquitetura REST</li>
</ol> </ol>
</ol> </ol>
</nav> </nav>

View File

@ -1,3 +1,7 @@
<script>
import VscUsingMySqlConnector from "../../assets/img/49_vsc_using_mysqlconnector.png";
</script>
<svelte:head> <svelte:head>
<title>Conectando com um banco de dados</title> <title>Conectando com um banco de dados</title>
</svelte:head> </svelte:head>
@ -10,42 +14,769 @@
</nav> </nav>
<p> <p>
Os endpoints que escrevemos no capítulo anterior não fazem nada além de exibir os dados que você envia na requisição. 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 eles <em>persistam</em> os dados enviados, vamos utilizar um banco de dados. 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 esse 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>
<p> <p>
Vamos utilizar um banco de dados <code>MySQL</code>, 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 <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> </p>
<ul>
<li>Usuário: <code>sistemasdistribuidos.aluno</code></li>
<li>Senha: <code>eW03avS7M8kOUL1A9bZWW2RTIfzEI1Di</code></li>
</ul>
<h2>MySqlConnector</h2> <h2>MySqlConnector</h2>
<p> <p>
Para gerenciar conexões e realizar consultas, vamos utilizar a biblioteca <a href="https://mysqlconnector.net/" target="_blank">MySqlConnector</a>. Abra Abra o terminal no seu projeto, e execute o comando:
o terminal no seu projeto, e execute o comando:
</p> </p>
<pre><code>{`cd Biblioteca <pre><code>{`cd Biblioteca
dotnet add package MySqlConnector`}</code></pre> dotnet add package MySqlConnector`}</code></pre>
<p> <p>
Note que o comando precisa ser executado <em>no diretório onde existe o arquivo <code>Biblioteca.csproj</code></em>, 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,
por isso o comando <code>cd Biblioteca</code>. 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>{`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,`}</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/{isbn}", () =>
{
});
// Obtém os dados de um livro individual.
app.MapGet("/livros/{isbn}", () =>
{
});
// Remove um livro.
app.MapDelete("/livros/{isbn}", () =>
{
});
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 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 do dotnet 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 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;
}`}</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
é praticamente ubíqua no dotnet, 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 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()
{
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),
});
}`}</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 Isbn, Titulo, ...</code>), a coluna <code>Isbn</code> é a primeira,
portanto, para ler este valor, devemos executar <code>reader.GetString(0)</code>. <code>Titulo</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 pelo ISBN</h2>
<p>
O ISBN é 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(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),
};
}`}</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 Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Isbn=@isbn";
cmd.Parameters.AddWithValue("isbn", isbn);`}</code></pre>
<p>
A consulta é montada na variável <code>cmd</code>, o parâmetro <code>isbn</code> é adicionado ao comando.
</p>
<pre><code>{`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),
};`}</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 ISBN, executamos <code>await reader.ReadAsync()</code> apenas uma vez.
Se este método retorna <code>false</code>, é 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 <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/{isbn}", async (string isbn) =>
{
var repo = new LivroRepository();
var res = await repo.Obter(isbn);
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.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;
}`}</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.Isbn == "")
{
throw new Exception("O ISBN do livro é obrigatório.");
}
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);`}</code></pre>
<p>
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 <code>livro</code>.
</p>
<pre><code>{`await cmd.ExecuteNonQueryAsync();
return livro;`}</code></pre>
<p>
Em seguida, executamos o insert através de <code>await cmd.ExecuteNonQueryAsync()</code>. Como a operação é de escrita, não há resultado a ser lido.
</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(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;
}`}</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(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;`}</code></pre>
<p>
Chamamos a função <code>Obter(string)</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 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;`}</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/{isbn}", async (string isbn, Livro livro) =>
{
var repo = new LivroRepository();
var res = await repo.Editar(isbn, 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(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();
}`}</code></pre>
<p>
Vamos adicioná-lo agora ao seu endpoint.
</p>
<pre><code>{`// Remove um livro.
app.MapDelete("/livros/{isbn}", async (string isbn) =>
{
var repo = new LivroRepository();
await repo.Desativar(isbn);
});`}</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> </p>
<ul> <ul>
<li>Limpar Program.cs</li> <li>
<li>criar arquivo Models/Livro.cs</li> O endpoint de listagem está sempre obtendo a primeira página da consulta. Tente fazer seu endpoint receber um parâmetro de url
<li>criar arquivo Repositories/LivroRepository.cs</li> (como <code>/livros?pagina=1</code>), para que seja possível obter as próximas páginas além da primeira.
<li>criar método de inserção</li> </li>
<li>testar método de inserção</li> <li>
<li>criar métodos de consulta</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
<li>testar métodos de consulta</li> que os livros inativos não apareçam. Como você poderia fazer isso?
</li>
</ul> </ul>
</section> </section>

View File

@ -24,7 +24,6 @@
<ul> <ul>
<li><em>Visualizar</em> os livros da biblioteca disponíveis no sistema.</li> <li><em>Visualizar</em> os livros da biblioteca disponíveis no sistema.</li>
<li><em>Cadastrar</em>, <em>editar</em> e <em>remover</em> livros no sistema.</li> <li><em>Cadastrar</em>, <em>editar</em> e <em>remover</em> livros no sistema.</li>
<li>Registrar quando um livro foi <em>emprestado</em> para um aluno.</li>
</ul> </ul>
<p> <p>

View File

@ -19,7 +19,7 @@
<ul> <ul>
<li><em>Visualizar</em> os livros da biblioteca disponíveis no sistema.</li> <li><em>Visualizar</em> os livros da biblioteca disponíveis no sistema.</li>
<li><em>Cadastrar</em>, <em>editar</em> e <em>remover</em> livros no sistema.</li> <li><em>Cadastrar</em>, <em>editar</em> e <em>remover</em> livros no sistema.</li>
<li>Registrar quando um livro foi <em>emprestado</em> para um aluno.</li> <!-- <li>Registrar quando um livro foi <em>emprestado</em> para um aluno.</li> -->
</ul> </ul>
<p> <p>