Azure Cosmos DB is a fully managed (PaaS) NoSQL which means it can handle unstructured data types.  The Azure Cosmos DB is a relational database that is globally distributed and provides high availability and scalability, it provides you guaranteed speed and performance.

You can learn more about the repository pattern by first reading the Generic Repository Pattern C# article.

Creating an Azure Cosmos DB account

1. Type Cosmos DB into the Azure portal search bar and clicking
+ Create and choose Azure Cosmos DB for NoSQL as
shown in the following screenshot.

Creating azure cosmos db account using azure portal

2. In the Create Azure Cosmos DB Account page, enter the basic settings for the new Azure Cosmos DB account as
shown in the following screenshot.

create azure cosmos db account

3. Select Review + create.

4.  Navigate to Azure Cosmos DB Account  and select the Keys menu. Copy
the URI and PRIMARY KEY values, which we need to  connect
to the Cosmos DB account from our application.

 URI and PRIMARY KEY values

Creating a class library project and adding Models

Our sample relational database for storing information about staff contain the following entities that will be stored in  our azure cosmos db containers, as shown in the diagram below.

Models

1. Open Visual Studio 2022 and click the Create a new project button.

2. Select the Class library project and click the Next button.

3. Enter CS.Staff.Models in the Project name textbox and Staff in the Solution name and click the Next button.

4. Select .NET 6.0 as the version of the Framework to use and click the Create button.

5. Right-click theCS.Staff.Modelsproject and add a new class file named Department.

namespace CS.Staff.Models
{
    public class Department
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; } 
        public string Name { get; set; } 
        public string Location { get; set; }
        public List<Employee> Employees { get; set; } 
        [JsonProperty("_etag")]
        public string Etag { get; set; }
    }
}

6. Right-click theCS.Staff.Modelsproject and add a new class file named Employee.

namespace CS.Staff.Models
{
    public class Employee
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; } 
        public string FirstName { get;set; } 
        public string LastName { get; set; } 
        public string Job { get; set; }
        public string Email { get; set; }
        public DateTime HireDate { get; set; } 
        public decimal Salary { get; set; }
        public List<Project> Projects { get; set; } 
        [JsonProperty("_etag")]
        public string Etag { get; set; }
    }
}

7. Right-click theCS.Staff.Modelsproject and add a new class file named Project.

namespace CS.Staff.Models
{
    public class Project
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; } 
        public DateTime StartDate { get; set; } 
        public DateTime EndDate { get; set; }
        [JsonProperty("_etag")]
        public string Etag { get; set; }
    }
}

The id field is mandatory field for all entities. 

Creating a Database and Containers

1. Navigate to the created Azure Cosmos DB account and Select Data Explorer and then select New Container.

2. Enter the settings for the Departments container as shown in the following screenshot.

Create department table

We use  /Name  as the partition key in the Departments container.

3. Add the Employees container, as shown in the screenshot below.

Create employee container

We use  /Email  as the partition key in the Employees container.

4. Add the Projects container, as shown in the screenshot below.

azure cosmos db,Azure Cosmos DB Repository Pattern

We use  /Id  as the partition key in the Projects container.

Creating a class library and implementing the Repository pattern

The Repository pattern is a Domain-Driven Design pattern that provides an abstraction of data that separates the data layer from the rest of the application.

1. Right-click the solution and select the Add, New Project option from the menu.

2. Select the Class library project and click the Next button.

3. Enter CS.Staff.Repositories in the Project name textbox and click the Next button.

4. Select .NET 6.0 as the version of the Framework to use and click the Create button.

project now

5. Install the following NuGet package :

  • Microsoft.Azure.Cosmos

Also read https://dotnetcoder.com/creating-a-blazor-confirm-dialog-component/

Implementing the Repository pattern

1. Right click the CS.Staff.Repositories project and add a new folder named Interfaces, and add an interface file named IBaseRepository<TItem>

namespace CS.Staff.Repositories.Interfaces
{
    public interface IBaseRepository<TItem> where TItem : class 
    {
        Task<IEnumerable<TItem>> GetItemsAsync(string filter); 
        Task<TItem> FindItemAsync(string id, string partionKey);
        Task<TItem> AddItemAsync(TItem item, string partitionKey);
        Task<TItem> UpdateItemAsync(TItem item, string id, string etag, string partitionKey);
        Task<bool> RemoveItemAsync(string id, string partitionKey);
    }
}

The generic IBaseRepository is common interface for working with any of objects.

2. Add a new class file named BaseRepository<TItem> to the project, that implements the IBaseRepository<TItem> interface.

namespace CS.Staff.Repositories
{
    public class BaseRepository<TItem> : IBaseRepository<TItem> where TItem : class
    {
        private readonly Container container;
        public BaseRepository(CosmosClient cosmosClient, string databaseId, string containerId)
        {
            container = cosmosClient.GetContainer(databaseId, containerId);
        }
        public async Task<TItem> AddItemAsync(TItem item, string partitionKey)
        {
            return await container.CreateItemAsync<TItem>(item, new PartitionKey(partitionKey)).ConfigureAwait(false); 
        }
        public async Task<TItem> FindItemAsync(string id, string partionKey)
        {
            try
            {
                ItemResponse<TItem> item = await container.ReadItemAsync<TItem>(id, new PartitionKey(partionKey)).ConfigureAwait(false);
                return item;
            } catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return null;
            }
        }
        public async Task<IEnumerable<TItem>> GetItemsAsync(string filter)
        {
            filter = string.IsNullOrEmpty(filter) ? "select * from item" : $"select * from item where {filter}";
            var filteredFeed = container.GetItemQueryIterator<TItem>(new QueryDefinition(filter));
            List<TItem> items = new();
            while (filteredFeed.HasMoreResults)
            {
                var response = await filteredFeed.ReadNextAsync().ConfigureAwait(false);
                items.AddRange(response.ToList());
            }
            return items;
        }
        public async Task<TItem> UpdateItemAsync(TItem item, string id, string etag, string partitionKey)
        {
            try
            {
                return await container.ReplaceItemAsync<TItem>(item, id, new PartitionKey(partitionKey), new ItemRequestOptions { IfMatchEtag = etag }).ConfigureAwait(false);
            }
            catch(CosmosException ex)
            {
                throw new InvalidOperationException(ex.Message);
            }
        }
        public async Task<bool> RemoveItemAsync(string id, string partitionKey)
        {
            try
            {
                ItemResponse<TItem> item = await container.DeleteItemAsync<TItem>(id, new PartitionKey(partitionKey)).ConfigureAwait(false);
                return true;
            }
            catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return false;
            }
        }
    }
}

Since we don’t need to access the original context, we use  ConfigureAwait(false) in all awaited methods.

ItemResponse<TItem> item = await container.ReadItemAsync<TItem>(id, new PartitionKey(partionKey)).ConfigureAwait(false);

We have declared a private variable of type Microsoft.Azure.Cosmos.Container at the top that points to a corresponding container based on the containerId parameter.

private readonly Container container;

The BaseRepository constructor receives an instance of CosmosClientusing Dependency Injection, and then we initialize the private member container withGetContainer(databaseId, containerId)

public BaseRepository(CosmosClient cosmosClient, string databaseId, string containerId)
{
    container = cosmosClient.GetContainer(databaseId, containerId);
}

The other five methods are self-explanatory code that wraps around the Azure Cosmos container.

3.  Right click the  Interfaces folder and add three interface files named IDepartmentRepositoryIEmployeeRepository and IProjectRepository (see below).

namespace CS.Staff.Repositories.Interfaces
{
    public interface IDepartmentRepository :IBaseRepository<Department>
    {
    }
}
namespace CS.Staff.Repositories.Interfaces
{
    public interface IEmployeeRepository :IBaseRepository<Employee>
    {
    }
}

These interfaces do not add any functionality beyond what’s provided in the IBaseRepository.

4. Right click the CS.Staff.Repositories project and add a new class file named DatabaseSettings as shown below.

namespace CS.Staff.Repositories
{
    public class DatabaseSettings
    {
        public string DatabaseId { get; set; } 
        public string ContainerId { get; set; }
    }

This class contains information related to the container and the database.

5. Right click the CS.Staff.Repositories project and add a new class file named DepartmentRepository as shown below.

namespace CS.Staff.Repositories
{
    public class DepartmentRepository : BaseRepository<Department>, IDepartmentRepository
    {
        public DepartmentRepository(CosmosClient cosmosClient, DatabaseSettings settings) 
            : base(cosmosClient, settings.DatabaseId, settings.ContainerId)
        {
        }
    }
}

The DepartmentRepository implements the IBaseRepository and the IDepartmentRepository.

It receives CosmosClientinstance and DatabaseSettings instance using Dependency Injection.

3. Similarly, dd two class files called EmployeeRepository and ProjectRepository (see below).

namespace CS.Staff.Repositories
{
    public class EmployeeRepository :BaseRepository<Employee>, IEmployeeRepository
    {
        public EmployeeRepository(CosmosClient cosmosClient, DatabaseSettings settings) 
            : base(cosmosClient, settings.DatabaseId, settings.ContainerId)
        {
        }
    }
}
namespace CS.Staff.Repositories
{
    public class ProjectRepository : BaseRepository<Project>, IProjectRepository
    {
        public ProjectRepository(CosmosClient cosmosClient, DatabaseSettings settings)
            : base(cosmosClient, settings.DatabaseId, settings.ContainerId)
        {
        }
    }
}

Creating ASP.NET Core API

1. Create an ASP.NET Core Web API project using Visual Studio with the name CS.Staff.ApiApp

create api app

No we need to Add our azure cosmos db models and repositories packages to our Api App.

1. In Solution Explorer, right click the CS.Staff.ApiApp project’s Dependencies node, and select Add Project Reference.

 In the Reference Manager dialog, select the CS.Staff.Repositories project, and select OK.

2. Open the appsettings.Development.json file and add the information we need to connect
to the Cosmos DB account from our app URI , Primary Key , and container names .

{
  "AccountEndpoint": "YOU ACCOUNT ENDPOINT",
  "AuthKey": "YOUR PRIMARY KEY",
  "DatabaseId": "cs-staff-database",
  "DepartmentContainer": "Departments",
  "EmployeeContainer": "Employees",
  "ProjectContainer":  "Projects"
}

3. Add a project folder named Extensions and add a new class file called RepositoryExtensions

namespace CS.Staff.ApiApp.Extensions
{
    public static class RepositoryExtensions
    {
        public static IServiceCollection AddDepartmentRepository(this IServiceCollection services , Action<DatabaseSettings> configureSettings)
        {
            return services.AddScoped<IDepartmentRepository>(serviceProvider =>
            {
                var settings = new DatabaseSettings(); 
                configureSettings(settings); 
                return ActivatorUtilities.CreateInstance<DepartmentRepository>(serviceProvider , settings); 
            });
        }
        public static IServiceCollection AddEmployeeRepository(this IServiceCollection services, Action<DatabaseSettings> configureSettings)
        {
            return services.AddScoped<IEmployeeRepository>(serviceProvider =>
            {
                var settings = new DatabaseSettings();
                configureSettings(settings);
                return ActivatorUtilities.CreateInstance<EmployeeRepository>(serviceProvider, settings);
            });
        }
        public static IServiceCollection AddProjectRepository(this IServiceCollection services, Action<DatabaseSettings> configureSettings)
        {
            return services.AddScoped<IProjectRepository>(serviceProvider =>
            {
                var settings = new DatabaseSettings();
                configureSettings(settings);
                return ActivatorUtilities.CreateInstance<ProjectRepository>(serviceProvider, settings);
            });
        }
    }
}

4. Add the repositories to the Dependency Injection Container with DatabaseSettings configuration.

5. Add three controllers  that will interact with azure cosmos db to the Controllers folder (see below).

DepartmentController

// Add services to the container.
builder.Services.AddControllers();
var configuration = builder.Configuration;
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
{
    SerializerOptions = new() { IgnoreNullValues = true },
};
builder.Services.AddSingleton(s => new CosmosClient(configuration.GetValue<string>("AccountEndpoint"), configuration.GetValue<string>("AuthKey"), cosmosClientOptions));
builder.Services.AddDepartmentRepository(settings =>
{
    settings.DatabaseId = configuration.GetValue<string>("DatabaseId");
    settings.ContainerId = configuration.GetValue<string>("DepartmentContainer"); ;
});
builder.Services.AddEmployeeRepository(settings =>
{
    settings.DatabaseId = configuration.GetValue<string>("DatabaseId");
    settings.ContainerId = configuration.GetValue<string>("EmployeeContainer"); ;
});
builder.Services.AddProjectRepository(settings =>
{
    settings.DatabaseId = configuration.GetValue<string>("DatabaseId");
    settings.ContainerId = configuration.GetValue<string>("ProjectContainer"); ;
});
namespace CS.Staff.ApiApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DepartmentController : ControllerBase
    {
        private readonly IDepartmentRepository departmentRepository;
        public DepartmentController(IDepartmentRepository departmentRepository)
        {
            this.departmentRepository = departmentRepository;
        }
        [HttpGet]
        public async Task<IEnumerable<Department>> GetDepartmentsAsync(string filter)
        {
            return await departmentRepository.GetItemsAsync(filter).ConfigureAwait(false); 
        }
        [HttpGet]
        [Route("{id}")]
        public async Task<Department> GetDepartmentById(string id, [FromQuery][Required] string partitionKey)
        {
            return await departmentRepository.FindItemAsync(id, partitionKey).ConfigureAwait(false);
        }
        [HttpPost]
        public async Task<Department> CreateDepartmentAsync([FromBody] Department department)
        {
            if (department == null || department.Etag != null)
            {
                return null;
            }
            return await departmentRepository.AddItemAsync(department, department.Location).ConfigureAwait(false);
        }
        [HttpPut]
        public async Task<Department> UpdateDepartmentAsync([FromBody] Department department)
        {
            return await departmentRepository.UpdateItemAsync(department, department.Id,department.Etag, department.Location ).ConfigureAwait(false);
        }
        [HttpDelete] 
        public async Task<bool> RemoveDepartmentAsync(string id, [FromQuery][Required] string partitionKey)
        {
            return await departmentRepository.RemoveItemAsync(id, partitionKey).ConfigureAwait(false);
        }
    }
}

EmployeeController

namespace CS.Staff.ApiApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        private readonly IEmployeeRepository employeeRepository;
        public EmployeeController(IEmployeeRepository employeeRepository)
        {
            this.employeeRepository = employeeRepository;
        }
        [HttpGet]
        public async Task<IEnumerable<Employee>> GetEmployeesAsync(string filter)
        {
            return await employeeRepository.GetItemsAsync(filter).ConfigureAwait(false);
        }
        [HttpGet]
        [Route("{id}")]
        public async Task<Employee> GetEmployeeById(string id, [FromQuery][Required] string partitionKey)
        {
            return await employeeRepository.FindItemAsync(id, partitionKey).ConfigureAwait(false);
        }
        [HttpPost]
        public async Task<Employee> CreateEmployeeAsync([FromBody] Employee employee)
        {
            return await employeeRepository.AddItemAsync(employee, employee.Email).ConfigureAwait(false);
        }
        [HttpPut]
        public async Task<Employee> UpdateEmployeeAsync([FromBody] Employee employee)
        {
            return await employeeRepository.UpdateItemAsync(employee, employee.Id, employee.Etag, employee.Email).ConfigureAwait(false);
        }
        [HttpDelete]
        public async Task<bool> RemoveEmployeeAsync(string id, [FromQuery][Required] string partitionKey)
        {
            return await employeeRepository.RemoveItemAsync(id, partitionKey).ConfigureAwait(false);
        }
    }
}

ProjectController

namespace CS.Staff.ApiApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProjectController : ControllerBase
    {
        private readonly IProjectRepository projectRepository;
        public ProjectController(IProjectRepository projectRepository)
        {
            this.projectRepository = projectRepository;
        }
        [HttpGet]
        public async Task<IEnumerable<Project>> GetProjectsAsync(string filter)
        {
            return await projectRepository.GetItemsAsync(filter).ConfigureAwait(false);
        }
        [HttpGet]
        [Route("{id}")]
        public async Task<Project> GetProjectById(string id, [FromQuery][Required] string partitionKey)
        {
            return await projectRepository.FindItemAsync(id, partitionKey).ConfigureAwait(false);
        }
        [HttpPost]
        public async Task<Project> CreateProjectAsync([FromBody] Project project)
        {
            return await projectRepository.AddItemAsync(project, project.Id).ConfigureAwait(false);
        }
        [HttpPut]
        public async Task<Project> UpdateProjectAsync([FromBody] Project project)
        {
            return await projectRepository.UpdateItemAsync(project, project.Id, project.Etag, project.Id).ConfigureAwait(false);
        }
        [HttpDelete]
        public async Task<bool> RemoveProjectAsync(string id, [FromQuery][Required] string partitionKey)
        {
            return await projectRepository.RemoveItemAsync(id, partitionKey).ConfigureAwait(false);
        }
    }
}

6. Run the application and test it.

Run the solution

6. Create a Department with related Employees and retrieve it from the azure cosmos db database.

Create entity
response cosmos db

The code for the demo can be found  Here

Also read https://dotnetcoder.com/creating-a-blazor-dropdown-list-component/

Enjoy This Blog?

Buy Me a Coffee Donate via PayPal

Discover more from Dot Net Coder

Subscribe to get the latest posts sent to your email.

Author

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
Best Wordpress Adblock Detecting Plugin | CHP Adblock