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": "*"
}
- [Código] Agora criaremos uma classe denominada
DatabaseSettings
que irá conter os parâmetros equivalentes aos que criamos noappsettings.json
. Esta classe irá armazenar os valores de propriedade doappsettings.json
.
public class DatabaseSettings
{
public string ConnectionString { get; set; } = null!;
public string DatabaseName { get; set; } = null!;
public string CollectionName { get; set; } = null!;
}
- [Código] No
Program.cs
adicionaremos a linha a seguir. A instância de configuração da seçãoMongoDatabase
presente no arquivoappsettings.json
é registrada no contêiner de injeção de dependência.
...
builder.Services.Configure<DatabaseSettings>(
builder.Configuration.GetSection("MongoDatabase"));
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!;
}
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 Name
do 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);
}
...
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>();
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 deMongoClient
e de acordo com as diretrizes oficiais do MongoDB, recomenda-se armazenar uma instância doMongoClient
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);
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);
}
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();
}
}
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();
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
- Pacote Nuget: MongoDB.Driver
- Linguagem: C# .NET 8.0
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 (0)