Azure Cosmos DB for NoSQL
is a fully managed and serverless NoSQL
and vector database
for modern app development, including AI applications and agents. With its SLA-backed speed and availability as well as instant dynamic scalability, it is ideal for real-time NoSQL applications that require high performance and distributed computing over massive volumes of NoSQL and vector data.
Soruce: learn.microsoft.com, you can learn more about it from here
“OpenAI relies on Cosmos DB to dynamically scale their ChatGPT service – one of the fastest-growing consumer apps ever – enabling high reliability and low maintenance.” – Satya Nadella, Microsoft chairman and chief executive officer Source: learn.microsoft.com
Let’s do some real work. Most of you are not here to learn the theorotical aspects. Right? Off course.. So.. Create an azure account first, If you haven’t created. You can create a trial account with some credit for 30 days. After that let’s create an cosmos db resource.
Lets create cosmos db resource in azure
I am creating this resource for the demo, I am trying to save every penny here. You need to change the options if you are looking for a scalable production database.
First and foremost, log-in to the Azure portal
. Go to the Databases
section and select Azure Cosmos DB
In the next step, you will notice a lot of options, we need to select Azure Cosmos DB for NoSql
.
Note that, you need to select a resource group
if it is not already created. I have created a resource group named rg_CosmosDemo
. You can easily cleanup the resource in this way. When you are done playing with cosmos db, just delete the resource group named rg_CosmosDemo
and it will remove the CosmosDb
resource along with it.
📢 Note: I am selecting the option serverless
for the sake of the demo. It is a very cheap option while you are learning.
Click on the Create
button and the cosomos db resource will be created soon, you have to wait a bit.
After that, go to that resource and let’s check the security of the resource.
Securing cosmos db account
Only give public access to yourself (i.e public ip address of your manchine)
Now we will create a database after this.
Create a database
Create a container
Think container as a table
in the terminology of sql
- and collection
in the terminology of mongo db
.
Database id: database name which we have created earlier.
Container id: name of container
Partition key: It creates a logical partition in our container. Let’s say you have a key name
Country
in thepeople
container. If we makeCountry
as a partition key, then all the records will be logically partitioned by theCountry
. It helps to retrieve data faster, you just have to look up in the specifict partition. Right now we are making a fieldid
as a partition key. It will evenly distribute data in each partition. Note that I have provided a value\id
, which is correct, we need to prepend\
.
Do not select Add hierarchical key
.
After that, just create the container.
Let’s make a query to the container.
CosmosDb CRUD with dotnet
Source code : https://github.com/rd003/DotnetPracticeDemos/tree/master/AzureDemos/CosmosDemo
Creating a new project
Execute these commands in a sequence (one-by-one)
# Create a solution
dotnet new sln -o CosmosDemo
# Change director
cd CosmosDemo
# Create new blank .net project
dotnet new web -n CosmosDemo.Api
# add project to solution
dotnet sln add CosmosDemo.Api/CosmosDemo.Api.csproj
# open project in vs code
code .
Nuget packages
dotnet add package Microsoft.Azure.Cosmos
Let’s find the connection string
Copy the Primary Connection String
’s value.
appsettings.json
"CosmosDb": {
"ConnectionString": "",
"Database": "PersonDb"
}
Do not expose ConnectionString
string to any one. In dev environment we must put it in a secrets-manager
, which is stored in our machine, not in a project. In production, you need to put that ConnectionString
in the environment variable
of the AppService
(where your project is deployed).
Let’s save our ConnectionString
in the the secrets-manager
.
# Visit to a project directory
cd CosmosDemo.Api
# Initialize
dotnet user-secrets init
You will notice a line in CosmosDemo.Api.csproj
:
<UserSecretsId>c2bfad31-ffe4-41a9-88e0-715fba6a41bd</UserSecretsId>
Add a secret:
dotnet user-secrets set "CosmosDb:ConnectionString" "<your-connection-string>"
Path of a secret
- Windows:
%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json
- Linux/Mac:
~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
📢 Note: Get user_secret_id from CosmosDemo.Api.csproj
(c2bfad31-ffe4-41a9-88e0-715fba6a41bd)
Model
For the sake of demo, I have created Person
class in the Program.cs
// Program.cs
public class Person
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; } = Guid.NewGuid().ToString();
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
}
We need to store id
in lowercase, so we need to decorate it with JsonProperty
Create a method in Program.cs
Make sure to include these two namespaces.
using Microsoft.Azure.Cosmos;
using Newtonsoft.Json;
Method to get container:
// Program.cs
static Container GetContainer(IConfiguration config)
{
string connectionString = config["CosmosDb:ConnectionString"] ?? throw new ConfigurationErrorsException("CosmosDb:ConnectionString");
var client = new CosmosClient(connectionString);
string databaseName = config["CosmosDb:Database"] ?? throw new ConfigurationErrorsException("CosmosDb:Database");
var database = client.GetDatabase(databaseName);
var contaier = database.GetContainer("people");
return contaier;
}
We need to get a container in every single endpoint, that is why I am creating this method.
Endpoints
All the endpoints are defined in Program.cs
. Which is not a best practice, but it is a demo so I am doing it.
POST
app.MapPost("/api/people", async (IConfiguration config, Person person) =>
{
var container = GetContainer(config);
ItemResponse<Person> response = await container.CreateItemAsync(item: person, partitionKey: new PartitionKey(person.Id));
Console.WriteLine($"====> RU {response.RequestCharge}");
return Results.Ok(person);
});
📢 Note: This line Console.WriteLine($"====> RU {response.RequestCharge}");
is used for checking the value of RU
. In this way you can figure out how costly our call is and optimize it if needed.
PUT
app.MapPut("/api/people", async (IConfiguration config, Person person) =>
{
var container = GetContainer(config);
ItemResponse<Person> response = await container.ReplaceItemAsync(person, person.Id, partitionKey: new PartitionKey(person.Id));
Console.WriteLine($"====> RU {response.RequestCharge}");
return Results.Ok(person);
});
GET ALL
app.MapGet("/api/people", async (IConfiguration config) =>
{
var container = GetContainer(config);
var iterator = container.GetItemQueryIterator<Person>();
var people = new List<Person>();
while (iterator.HasMoreResults)
{
var response = await iterator.ReadNextAsync();
Console.WriteLine($"====> RU {response.RequestCharge}");
people.AddRange(response);
}
return Results.Ok(people);
});
Filter the records
Let’s say we want to retrieve records where FirstName
is “John”.
app.MapGet("/api/people", async (IConfiguration config, string firstName) =>
{
var container = GetContainer(config);
string query = "SELECT * FROM people p WHERE p.FirstName = @firstName";
var queryDefinition = new QueryDefinition(query)
.WithParameter("@firstName", firstName);
var iterator = container.GetItemQueryIterator<Person>(queryDefinition: queryDefinition);
var people = new List<Person>();
while (iterator.HasMoreResults)
{
var response = await iterator.ReadNextAsync();
Console.WriteLine($"====> RU {response.RequestCharge}");
people.AddRange(response);
}
return Results.Ok(people);
});
Get by id
app.MapGet("/api/people/{id}", async (IConfiguration config, string id) =>
{
var container = GetContainer(config);
ItemResponse<Person> response = await container.ReadItemAsync<Person>(id: id, partitionKey: new PartitionKey(id));
Console.WriteLine($"====> RU {response.RequestCharge}");
return Results.Ok(response.Resource);
});
Delete
app.MapDelete("/api/people/{id}", async (IConfiguration config, string id) =>
{
var container = GetContainer(config);
ItemResponse<Person> response = await container.DeleteItemAsync<Person>(id: id, partitionKey: new PartitionKey(id));
Console.WriteLine($"====> RU {response.RequestCharge}");
return Results.NoContent();
});