> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# Entity Framework Core Cosmos DB provider
- URL: https://www.azurebrasil.cloud/blog/entity-framework-core-cosmos-db-provider/
- Published: 2023-07-19T12:35:53.000Z
- Updated: 2023-07-19T12:35:53.000Z
- Author: Talles Valiatti

Nesse post, utilizaremos o [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/?ref=azurebrasil.cloud) em conjunto com o [Azure Cosmos DB](https://learn.microsoft.com/en-us/azure/cosmos-db/?ref=azurebrasil.cloud) para criar um pequena aplicação em [ASP.NET Core MVC](https://learn.microsoft.com/pt-br/aspnet/core/mvc/overview?view=aspnetcore-7.0&ref=azurebrasil.cloud) . No meu *setup*, vou utilizar o [Visual Studio For Mac](https://visualstudio.microsoft.com/pt-br/vs/mac/?ref=azurebrasil.cloud) para criar o aplicativo web e o Visual Studio Code para trabalhar com a [infraestrutura como código](https://learn.microsoft.com/pt-br/azure/azure-resource-manager/bicep/overview?tabs=bicep&ref=azurebrasil.cloud).

Antes de começar, recomendo a leitura de dois posts que já escrevi sobre Azure Cosmos e Azure bicep:

- [Introdução ao Azure Cosmos DB + ASP.NET Core. Parte 1](https://tallesvaliatti.com/introdu%C3%A7%C3%A3o-ao-azure-cosmos-db-asp-net-core-parte-1-da0ece0ac039?ref=azurebrasil.cloud);
- [Deploy de arquivos bicep com o Azure Pipelines](https://tallesvaliatti.com/deploy-de-arquivos-bicep-com-o-azure-pipelines-61943950a0af?ref=azurebrasil.cloud).

Vamos inicialmente criar um projeto web MVC em .NET 7: 

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-29-at-17.38-1.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-29-at-17.38-2.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-29-at-17.38-3.png)

Com o projeto criado, podemos adicionar o pacote NuGet [Microsoft.EntityFrameworkCore.Cosmos](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Cosmos?ref=azurebrasil.cloud):

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-30-at-12.26.png)

Vamos voltar no diretório raiz do projeto, criar um novo diretório chamado *infra* e um arquivo chamado *main.bicep* com o seguinte conteúdo:

```
@description('Cosmos DB account name')
param accountName string = 'cosmos-${uniqueString(resourceGroup().id)}'

@description('Location for the Cosmos DB account.')
param location string = resourceGroup().location

@description('The name for the SQL API database')
param databaseName string

@description('The name for the SQL API container')
param containerName string

resource account 'Microsoft.DocumentDB/databaseAccounts@2022-05-15' = {
  name: toLower(accountName)
  location: location
  properties: {
    enableFreeTier: true
    databaseAccountOfferType: 'Standard'
    consistencyPolicy: {
      defaultConsistencyLevel: 'Session'
    }
    locations: [
      {
        locationName: location
      }
    ]
  }
}

resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2022-05-15' = {
  parent: account
  name: databaseName
  properties: {
    resource: {
      id: databaseName
    }
    options: {
      throughput: 1000
    }
  }
}

resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2022-05-15' = {
  parent: database
  name: containerName
  properties: {
    resource: {
      id: containerName
      partitionKey: {
        paths: [
          '/Id'
        ]
        kind: 'Hash'
      }
      indexingPolicy: {
        indexingMode: 'consistent'
        includedPaths: [
          {
            path: '/*'
          }
        ]
        excludedPaths: [
          {
            path: '/_etag/?'
          }
        ]
      }
    }
  }
}

```

Esse arquivo [bicep](https://learn.microsoft.com/pt-br/azure/azure-resource-manager/bicep/overview?tabs=bicep&ref=azurebrasil.cloud) basicamente irá criar um Azure Cosmos DB na camada [gratuita](https://learn.microsoft.com/en-my/azure/cosmos-db/free-tier?ref=azurebrasil.cloud). Como estou utilizando a [extensão bicep](https://learn.microsoft.com/pt-br/azure/azure-resource-manager/bicep/visual-studio-code?tabs=CLI&ref=azurebrasil.cloud), iremos fazer o *deploy* desse recurso de nuvem por meio do VS Code.

Antes do *deploy*, devemos criar um arquivo de [parâmetros](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/parameters?ref=azurebrasil.cloud):

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-30-at-12.22-1.png)

Vamos utilizar os seguintes valores para o nosso Azure Cosmos DB

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-30-at-12.22-2.png)

Com o arquivo de parâmetros criado, podemos fazer o *deploy.*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-30-at-12.23-1.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-30-at-12.23-2.png)

Como não temos nenhum [grupo de recursos](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal?ref=azurebrasil.cloud) criado, iremos criar um durante o *deploy* do arquivo bicep.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-06-30-at-12.23.png)

Vamos chamá-lo de: 

```
rg-app-eastus
```

Após a finalização, teremos a seguinte estrutura de arquivos e diretórios em nossa solução: 

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.20.44.png)

Voltando ao VS for Mac, iremos criar a entidade que será salva no Azure cosmos DB. Vamos chamá-la de *Folder:*

```
using System;
namespace App.Web.Models
{
	public class Folder
	{
		public Guid Id { get; set; }
        public Guid? ParentId { get; set; }
		public string Name { get; set; }

        public Folder(Guid? parentId, string name)
        {
            Id = Guid.NewGuid();
            ParentId = parentId;
            Name = name;
        }
    }
}

```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.21.52.png)

Durante o fluxo de exibição de dados na *View (*recomendo a leitura sobre o padrão de projeto [MVC](https://learn.microsoft.com/pt-br/aspnet/core/mvc/overview?view=aspnetcore-7.0&ref=azurebrasil.cloud)), iremos reordenar essa estrutura para uma árvore de dados.

Vamos criar a classe principal para o Entity Framework Core, o *CosmosContext* que herda de [*DbContext*](https://learn.microsoft.com/pt-br/dotnet/api/system.data.entity.dbcontext?view=entity-framework-6.2.0&ref=azurebrasil.cloud)*.*

```
using App.Web.Models;
using Microsoft.EntityFrameworkCore;

namespace App.Web.Data
{
	public class CosmosContext : DbContext
	{
        public CosmosContext(DbContextOptions<CosmosContext> options)
       : base(options)
        {
        }

        public DbSet<Folder> Folders { get; set; }

        protected override void
            OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Folder>(x =>
            {
                x.ToContainer("Folders");
                x.HasKey(x => x.Id);
                x.HasPartitionKey(x => x.Id);
            });
        }
    }
}
```

Observe que definimos como a entidade *Folder* é [modelada](https://learn.microsoft.com/en-us/ef/core/modeling/?ref=azurebrasil.cloud) no banco de dados Azure Cosmos DB. Definimos sua *Key, [Partition key](https://learn.microsoft.com/en-us/azure/cosmos-db/partitioning-overview?ref=azurebrasil.cloud)* e o *[container](https://learn.microsoft.com/en-us/azure/cosmos-db/resource-model?ref=azurebrasil.cloud#azure-cosmos-db-containers).* Eu já escrevi [alguns](https://tallesvaliatti.com/introdu%C3%A7%C3%A3o-ao-azure-cosmos-db-asp-net-core-parte-1-da0ece0ac039?ref=azurebrasil.cloud) posts sobre esse recursos de nuvem, vale a leitura!

Iremos criar um repositório (interface + classe), que será responsável por buscar entidades de *Folders* do banco de dados, além de criar novos registros.

```
using App.Web.Models;

namespace App.Web.Data.Repositories
{
	public interface IFolderRepository
	{
		Task<IEnumerable<Folder>> GetllAllAsync();

		Task AddAsync(Folder folder);
    }
}

```

```
using App.Web.Models;
using Microsoft.EntityFrameworkCore;

namespace App.Web.Data.Repositories
{
	public class FolderRepository : IFolderRepository
	{
        private readonly CosmosContext _cosmosContext;

        public FolderRepository(CosmosContext cosmosContext)
        {
            _cosmosContext = cosmosContext;
        }

        public async Task AddAsync(Folder folder)
        {
            await _cosmosContext.AddAsync(folder);
            await _cosmosContext.SaveChangesAsync();
        }

        public async Task<IEnumerable<Folder>> GetllAllAsync()
        {
            return await _cosmosContext.Folders.ToListAsync();
        }
    }
}

```

Esses itens ficarão em um diretório chamado *Data.*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.22.23.png)

Para trabalharmos com estruturas de árvore, devemos criar a classe *TreeNode<T>* e *TreeNodeHelper*

```
namespace App.Web.Utils
{
    public class TreeNode<T>
    {
        public T Id { get; set; } = default!;
        public T? ParentId { get; set; }
        public string Name { get; set; } = default!;
        public List<TreeNode<T>> Children { get; set; } = new List<TreeNode<T>>();
    }
}
```

```
namespace App.Web.Utils
{
    public static class TreeNodeHelper
    {

        public static IEnumerable<TreeNode<T>> FetchChildren<T>(this TreeNode<T> root, List<TreeNode<T>> nodes)
        {
            return nodes.Where(n =>
                n.ParentId is not null &&
                n.ParentId.Equals(root.Id));
        }

        public static void RemoveChildren<T>(this TreeNode<T> root, List<TreeNode<T>> nodes)
        {
            foreach (var node in root.Children)
            {
                nodes.Remove(node);
            }
        }

        public static TreeNode<T> BuildTree<T>(this TreeNode<T> root, List<TreeNode<T>> nodes)
        {
            if (nodes.Count == 0) { return root; }

            var children = root.FetchChildren(nodes).ToList();
            root.Children.AddRange(children);
            root.RemoveChildren(nodes);

            for (int i = 0; i < children.Count; i++)
            {
                children[i] = children[i].BuildTree(nodes);
                if (nodes.Count == 0) { break; }
            }

            return root;
        }
    }
}

```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.24.35.png)

Vale comentar que essa implementação de árvore de dados é uma das inúmeras implementações possíveis!

Para servir como modelo para a nossa *[View](https://learn.microsoft.com/pt-br/aspnet/mvc/overview/getting-started/introduction/adding-a-view?ref=azurebrasil.cloud)*, devemos criar a *HomeIndexViewModel*

```
using System.ComponentModel.DataAnnotations;

namespace App.Web.ViewModels
{
	public class HomeIndexViewModel
	{
		public Guid? ParentFolderId { get; set; }

		[Required]
		[Display(Name = "Folder Name")]
		public string? FolderName { get; set; } = default!;
	}
}
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.25.12.png)

Temos a nossa controladora:

```
using Microsoft.AspNetCore.Mvc;
using App.Web.Utils;
using App.Web.Data.Repositories;
using App.Web.ViewModels;
using App.Web.Models;

namespace App.Web.Controllers;

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IFolderRepository _folderRepository;

    public HomeController(ILogger<HomeController> logger, IFolderRepository folderRepository)
    {
        _logger = logger;
        _folderRepository = folderRepository;
    }

    public async Task<IActionResult> IndexAsync()
    {
        ViewBag.Data = await GenerateFoldersAsync();

        var viewModel = new HomeIndexViewModel();

        return View(viewModel);   
    }

    [HttpPost]
    public async Task<IActionResult> IndexAsync(HomeIndexViewModel viewModel)
    {
        if (ModelState.IsValid)
        {
            var folder = new Folder(viewModel.ParentFolderId, viewModel.FolderName!);

            await _folderRepository.AddAsync(folder);

            return RedirectToAction(nameof(Index));
        }

        ViewBag.Data = await GenerateFoldersAsync();

        return View(viewModel);
    }

    private async Task<List<TreeNode<Guid>>> GenerateFoldersAsync()
    {
        var folders = await _folderRepository.GetllAllAsync();

        var data = new List<TreeNode<Guid>>();

        foreach (var rootFolder in folders.Where(x => x.ParentId is null))
        {
            var rootData = new TreeNode<Guid>
            {
                Id = rootFolder.Id,
                ParentId = default,
                Name = rootFolder.Name,
            };

            data.Add(rootData.BuildTree<Guid>(
                folders.Select(x =>
                new TreeNode<Guid>
                {
                    Id = x.Id,
                    ParentId = x.ParentId ?? default,
                    Name = x.Name

                })
                .ToList()));
        }

        return data;
    }
}
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.27.00.png)

Ela basicamente possui duas *[actions](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/actions?view=aspnetcore-7.0&ref=azurebrasil.cloud#defining-actions):*

- *Index* (*GET*): Retorna dados de *Folders em uma árvore* de dados;
- Index (*POST*): Recebe dados via *HomeIndexViewModel,* faz as validações do Model, adiciona um novo registro de *Folder* ao banco de dados e redireciona para a *action Index (GET).*

Dito isso, podemos criar a [*pa*](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-7.0&ref=azurebrasil.cloud)[rtial view](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-7.0&ref=azurebrasil.cloud) *\_Folder.cshtml* e a *View* principal *Index.cshtml*

```
@using App.Web.Utils;
@model (List<TreeNode<Guid>> data, int index)

@{
    var data = Model.data;
    var index = Model.index;
    var paddingLeft = $"{(20 * index)}px";
}

@foreach (var item in data
       .OrderByDescending(x => x.Children.Any())
       .ThenBy(x => x.Name)
       .Select(x => new { Node = x, ComponentId = Guid.NewGuid() }))
{
    <div class="row">
        <div class="col" style="padding-left:@paddingLeft">
            <input class="justify-content-start" form-check-input folder" type="checkbox">
            <span>@(item.Node.Name)</span>
            <input class="form-check-input folderId" value="@item.Node.Id" type="hidden">
        </div>
        
    </div>
    @if (item.Node.Children.Any())
    {
        <partial name="_folder" model="(item.Node.Children, index + 1)" />

    }
}
```

```
@using App.Web.ViewModels;
@using App.Web.Utils
@model HomeIndexViewModel

@{
    ViewData["Title"] = "Home Page";
    var data = ViewBag.Data as List<TreeNode<Guid>>;
}

<div class="container">
    <form asp-action="Index">
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <input type="hidden" asp-for="ParentFolderId"/>
        <div class="row mb-3">
            <div class="col-4">
                <label asp-for="FolderName" class="form-label"></label>
                <input asp-for="FolderName" class="form-control">
                <span asp-validation-for="FolderName" class="text-danger"></span>
            </div>
        </div>
        <div class="row mb-3">
            <div class="col-12">
                <partial name="_Folder" model="(data, 0)" />
            </div>
        </div>
        <div class="row">
            <div class="col-2">
                <button class="btn btn-primary" type="submit">
                    Add folder
                </button>
            </div>
        </div>
    </form>
</div>

```

No arquivo *site.js*, devemos adicionar o seguinte código jQuery:

```
var cleanSelection = function ()
{
    $(this).prop('checked', false);
}

var onClick = function () {

    var checked = $(this).is(":checked");
    
    // Clean all checkboxes
    $("input:checkbox.folder").each(cleanSelection)

    $(this).prop('checked', checked);

    if (checked) {
        var parent = $(this).parent();
        var children = parent.children();
        var parentId = children.filter(".folderId")[0].value;

        $("#ParentFolderId").val(parentId);
    }
    else {
        $("#ParentFolderId").val(null);
    }
};

$("input[type=checkbox]").on("click", onClick);
```

Temos a seguinte estrutura de arquivos:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.45.59.png)

Para finalizar o desenvolvimento, devemos adicionar o seguinte trecho de código no *Program.cs.* Isso é necessário para configurar a [injeção de dependência](https://learn.microsoft.com/pt-br/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0&ref=azurebrasil.cloud) do repositório *IFolderRepository* e o Azure Cosmos DB como banco de dados para o Entity Framework Core.

```
builder.Services.AddScoped<IFolderRepository, FolderRepository>();

builder.Services.AddDbContext<CosmosContext>(opt =>
    opt.UseCosmos(
        connectionString: "YOUR-CONNECTION-STRING",
        databaseName: "MyDb"));
```

O valor da *[connection string](https://learn.microsoft.com/en-us/azure/cosmos-db/scripts/cli/common/keys?ref=azurebrasil.cloud)* pode ser obtido por meio do portal do Azure, como mostrado na imagem abaixo: 

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Group-1133.png)

Vamos executar o projeto e adicionar algumas instâncias de *Folder:*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.45.32.png)

a

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Jul-04-2023-15-56-42.gif)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2023/07/Screen-Shot-2023-07-04-at-15.58.14.png)

Tudo funcionou corretamente!

Você já pode baixar o projeto por esse [link](https://github.com/TallesValiatti/EntityFrameworkCoreCosmosDBProvider?ref=azurebrasil.cloud), e não esquece de me seguir no [LinkedIn](https://www.linkedin.com/in/tallesvaliatti/?ref=azurebrasil.cloud)!

Até a próxima, abraços!