DEV Community

Cover image for Conceitos básicos de MongoDB utilizando C#/.NET | Fazendo seu primeiro CRUD
Larissa Tavares
Larissa Tavares

Posted on

Conceitos básicos de MongoDB utilizando C#/.NET | Fazendo seu primeiro CRUD

O MongoDB é um banco de dados não relacional, também chamado de NoSQL. É orientado a documentos, os quais podem ser descritos com dados no formato chave-valor utilizando o formato JSON (JavaScript Object Notation).

Os bancos de dados NoSQL, diferente dos bancos relacionais, não utilizam os esquemas de organização por meio de tabelas, linhas e colunas. E apresentam algumas vantagens como escalabilidade, flexibilidade, bom desempenho e facilidade para consultas.


Driver MongoDB C#

Neste exemplo criaremos uma API Web que fará as operações de CRUD (criar, ler, atualizar e deletar) no banco de dados NoSQL MongoDB.

Modelo de configuração

  • [Estação de trabalho] Instalar o MongoDB utilizando Docker: MongoDb Docker

  • [Código] Adicionar a dependência do pacote NuGet do MongoDB: MongoDB.Driver.

  • [Código] Inicialmente adicionaremos os valores de conexão do banco de dados no arquivo de configurações denominando appsettings.json. Passando informações como a string de conexão (ConnectionString), o banco de dados (DatabaseName) e a coleção (CollectionName) que iremos acessar no MongoDB.

appsettings.json

{
    "MongoDatabase": {
        "ConnectionString": "mongodb://localhost:27017",
        "DatabaseName": "BookStore",
        "CollectionName": "Books"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*"
}
Enter fullscreen mode Exit fullscreen mode
  • [Código] Agora criaremos uma classe denominada DatabaseSettings que irá conter os parâmetros equivalentes aos que criamos no appsettings.json. Esta classe irá armazenar os valores de propriedade do appsettings.json.
public class DatabaseSettings
{
    public string ConnectionString { get; set; } = null!;

    public string DatabaseName { get; set; } = null!;

    public string CollectionName { get; set; } = null!;
}
Enter fullscreen mode Exit fullscreen mode
  • [Código] No Program.cs adicionaremos a linha a seguir. A instância de configuração da seção MongoDatabase presente no arquivo appsettings.json é registrada no contêiner de injeção de dependência.
...
builder.Services.Configure<DatabaseSettings>(
    builder.Configuration.GetSection("MongoDatabase"));
Enter fullscreen mode Exit fullscreen mode

Em outras palavras, a propriedade ConnectionString de um objeto do tipo DatabaseSettings será populada com a propriedade MongoDatabase:ConnectionString do appsettings.json.


Modelo de entidade

  • [Código] A seguir criaremos nosso modelo de entidade que utilizaremos em todas as operações do CRUD.
public class Book
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string? Id { get; set; }

    [BsonElement("Name")]
    public string BookName { get; set; } = null!;

    public decimal Price { get; set; }

    public string Category { get; set; } = null!;

    public string Author { get; set; } = null!;
}
Enter fullscreen mode Exit fullscreen mode

Descrição das annotations:
[BsonId]: Designa a propriedade Id como chave primária.
[BsonRepresentation(BsonType.ObjectId)]: Permite a passagem do parâmetro como tipo string em vez de uma estrutura ObjectId. O MongoDB processa a conversão de string para ObjectId.
[BsonElement("Name")]: O valor Namedo atributo [BsonElement] representa o nome da propriedade da coleção do MongoDB.


Adicionando o serviço de operações CRUD

  • [Código]: Criar uma classe de serviço denominada BooksService.
      public class BooksService
   {
       private readonly IMongoCollection<Book> _booksCollection;

       public BooksService(IOptions<DatabaseSettings> bookStoreDatabaseSettings)
       {
           var mongoClient = new MongoClient(
               bookStoreDatabaseSettings.Value.ConnectionString);

           var mongoDatabase = mongoClient.GetDatabase(
               bookStoreDatabaseSettings.Value.DatabaseName);

           _booksCollection = mongoDatabase.GetCollection<Book>(
               bookStoreDatabaseSettings.Value.CollectionName);
       }
   ...
Enter fullscreen mode Exit fullscreen mode

No código acima temos nossa classe BooksService e seu construtor. Uma instância do DatabaseSettings é recuperada da injeção de dependência pela injeção do construtor. Utilizamos a interface IOptions pois já está registrado como Singleton e pode ser injetado em qualquer vida útil do serviço.

Em cenários onde as opções devem ser recalculadas a cada solicitação, podemos utilizar o IOptionsSnapshot. Sendo este registrado como Scoped.

Esta técnica fornece acesso para os valores de configuração do appsettings.json, que foi configurado no início deste artigo.

  • [Código]: Agora no Program adicionaremos a linha subsequente.
...
builder.Services.AddSingleton<BooksService>();
Enter fullscreen mode Exit fullscreen mode

Neste trecho a classe BooksService é registrada com a injeção de dependência para dar suporte à injeção de construtor nas classes consumidoras.

O tempo de vida Singleton é o mais apropriado pois a classe BooksService utiliza uma dependência direta de MongoClient e de acordo com as diretrizes oficiais do MongoDB, recomenda-se armazenar uma instância do MongoClient com o tipo de vida útil Singleton.

Singleton significa que um objeto do serviço é criado e fornecido para todas as requisições. Assim, todas as requisições obtém o mesmo objeto.

Voltando à classe BooksService, utilizamos os seguintes membros do MongoDB.Driver.

var mongoClient = new MongoClient(
        bookStoreDatabaseSettings.Value.ConnectionString);

var mongoDatabase = mongoClient.GetDatabase(
    bookStoreDatabaseSettings.Value.DatabaseName);

_booksCollection = mongoDatabase.GetCollection<Book>(
    bookStoreDatabaseSettings.Value.CollectionName);
Enter fullscreen mode Exit fullscreen mode

MongoClient lê a instância do servidor para executar operações de banco de dados e espera uma string de conexão como parâmetro.

Após o MongoClient se conectar a uma instância do MongoDB, o método GetDatabase é utilizado para acessar um banco de dados.

Já o método GetCollection é utilizado para acessar uma coleção. Na chamada para o GetCollection<TDocument>(collection) temos o TDocument sendo o tipo de objeto armazenado na coleção e o collection representando o nome da coleção.

  • [Código]: Adicionaremos os seguintes métodos na nossa classe BooksService.
public class BooksService
{
 ...
    public async Task<List<Book>> GetAsync() =>
    await _booksCollection.Find(_ => true).ToListAsync();

    public async Task<Book?> GetAsync(string id) =>
        await _booksCollection.Find(x => x.Id == id).FirstOrDefaultAsync();

    public async Task CreateAsync(Book newBook) =>
        await _booksCollection.InsertOneAsync(newBook);

    public async Task UpdateAsync(string id, Book updatedBook) =>
        await _booksCollection.ReplaceOneAsync(x => x.Id == id, updatedBook);

    public async Task RemoveAsync(string id) =>
        await _booksCollection.DeleteOneAsync(x => x.Id == id);
}
Enter fullscreen mode Exit fullscreen mode

Os seguintes métodos são acessados nesta classe:
Find: retorna todos os documentos na coleção que correspondem aos critérios de pesquisa fornecidos.
InsertOneAsync: insere o objeto fornecido como um novo documento na coleção.
ReplaceOneAsync: substitui o único documento que corresponde aos critérios de pesquisa fornecidos com o objeto fornecido.
DeleteOneAsync: exclui um único documento que corresponde aos critérios de pesquisa fornecidos.

Outros métodos que podem ser utilizados estão listados no link IMongoCollectionExtensions Methods.


Adicionar um controller

  • [Código]: No nosso último passo iremos adicionar um controlador utilizando o seguinte código.

Neste trecho temos métodos de ação GET, POST, PUT e DELETE, que darão suporte às nossas solicitações.

 [ApiController]
 [Route("api/[controller]")]
 public class BooksController : ControllerBase
 {
     private readonly BooksService _booksService;

     public BooksController(BooksService booksService) =>
         _booksService = booksService;

     [ProducesResponseType(404)]
     [ProducesResponseType(200)]
     [HttpGet]
     public async Task<List<Book>> Get() =>
     await _booksService.GetAsync();

     [ProducesResponseType(404)]
     [ProducesResponseType(200)]
     [HttpGet("{id:length(24)}")]
     public async Task<ActionResult<Book>> Get(string id)
     {
         var book = await _booksService.GetAsync(id);

         if (book is null)
         {
             return NotFound();
         }

         return book;
     }

     [ProducesResponseType(404)]
     [ProducesResponseType(200)]
     [HttpPost]
     public async Task<IActionResult> Post(Book newBook)
     {
         await _booksService.CreateAsync(newBook);

         return CreatedAtAction(nameof(Get), new { id = newBook.Id }, newBook);
     }

     [ProducesResponseType(404)]
     [ProducesResponseType(200)]
     [HttpPut("{id:length(24)}")]
     public async Task<IActionResult> Update(string id, Book updatedBook)
     {
         var book = await _booksService.GetAsync(id);

         if (book is null)
         {
             return NotFound();
         }

         updatedBook.Id = book.Id;

         await _booksService.UpdateAsync(id, updatedBook);

         return NoContent();
     }

     [ProducesResponseType(404)]
     [ProducesResponseType(200)]
     [HttpDelete("{id:length(24)}")]
     public async Task<IActionResult> Delete(string id)
     {
         var book = await _booksService.GetAsync(id);

         if (book is null)
         {
             return NotFound();
         }

         await _booksService.RemoveAsync(id);

         return NoContent();
     }
 }
Enter fullscreen mode Exit fullscreen mode

Após isso, iremos adicionar a chamada do método AddControllers e para o método MapControllers no nosso Program

...
builder.Services.AddControllers()
    .AddJsonOptions(
        options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
...
app.MapControllers();
Enter fullscreen mode Exit fullscreen mode

Com a alteração AddJsonOptions anterior, os nomes de propriedade na resposta JSON serializada da API Web correspondem aos respectivos nomes de propriedade no tipo de objeto.


Ferramentas utilizadas

Código no GitHub


Com isso finalizamos nossos entendimentos iniciais acerca de MongoDB e sobre como configurá-lo de maneira adequada. Além de termos bastante aparato teórico para entender muito sobre o nosso código. O código final está disponível no GitHub e seu link está disponível acima. Abraços!


Referências
Treinaweb - O que é MongoDB?
Microsoft Learn - Criar uma API Web com o ASP.NET Core e o MongoDB
MongoDB - Connection String
MongoDB - Databases and Collections
MongoDB - IMongoCollectionExtensions Methods

Top comments (1)