Azure C# Function — ComosDB trigger and in out binding

Michal Molka
3 min readMay 24, 2024

--

In the last post I’ve covered an Http request function: Azure Function — C# Http request function and CosmosDB

Today, we will look at a C# function invoked by a CosmosDB trigger. When a document in a Cosmos database is inserted or updated, then the function does its work.

Let’s walk through the code. We need to install two additional libraries Cosmos and Newtonsoft.Json.

using System;
using System.Collections.Generic;
using Microsoft.Azure.Documents;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Cosmos;
using Newtonsoft.Json;
using System.Threading.Tasks;

A Cosmos document is mapped to this class.

public class IowaSales
{
public string County { get; set; }
public string State { get; set; }
public int Quantity { get; set; }
public string FileName { get; set; }
public string OperationId { get; set; }
public string id { get; set; }
public bool isIowa { get; set; }
public string OperationType { get; set; }
};

A function in and out bindings:

  • CosmosDBTrigger — (in) parameters for the monitored Cosmos database and a container,
  • CosmosDB — (out) parameters for a Cosmos database and a container where we place a copy of a modified document.
public static class azure_function_cosmosdb_trigger
{
[FunctionName("azure_function_cosmosdb_trigger")]
public static async Task Run(
[CosmosDBTrigger(
databaseName: "iowa_db",
collectionName: "iowa_population",
ConnectionStringSetting = "cosmosdb_DOCUMENTDB",
LeaseCollectionName = "leases")]
IReadOnlyList<Document> input,
[CosmosDB(
databaseName: "iowa_db",
collectionName:"iowa_sales",
ConnectionStringSetting = "cosmosdb_DOCUMENTDB")]
IAsyncCollector<IowaSales> output,
ILogger log)

Long story short. In/out parameters are a syntactic sugar which you can use instead of creating connection objects in order to connect to a database.

As you see in the code above, we have a property ConnectionStringSetting = “cosmosdb_DOCUMENTDB”.

This parameter is defined in a local.settings.json file.

{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"cosmosdb_DOCUMENTDB": "AccountEndpoint=https://<your-cosmos-db-account>.documents.azure.com:443/;AccountKey=<your-cosmos-key>"
}
}

This isn’t everything. You need to declare this parameter in a Function App configuration section. Otherwise, function won’t fly after a deployment to Azure.

Now, we can step into a function logic. When a trigger is invoked, then all gathered documents are analyzed. In this case, an if condition checks whether a document “OperationType” property is a null value.

An input variable is used here — this is an input binding.

 if (input != null && input.Count > 0)
{
foreach (var item in input)
{
var jsonDocument = JsonConvert.DeserializeObject<IowaSales>(item.ToString());
if (jsonDocument.OperationType is null)
{

Then, the property is set.

log.LogInformation("The OperationType property is null");
jsonDocument.OperationType = "Upsert";

The document is placed inside a separate container. In this case an output variable is used. This is an example of output binding.

await output.AddAsync(jsonDocument);
log.LogInformation($"Document ID: {jsonDocument.id}");

On this stage, we didn’t have to use any Cosmos(Client/Database/Container) object to create a connection. So, we can try to do this. Let’s assume we want to add mentioned property to the document and upsert it into the same container.

We need a bit more code in order to connect to Cosmos.

string endpointUrl = System.Environment.GetEnvironmentVariable("endpointUrl");
string authorizationKey = System.Environment.GetEnvironmentVariable("authorizationKey");
string databaseName = System.Environment.GetEnvironmentVariable("databaseName");
string containerName = System.Environment.GetEnvironmentVariable("containerName");

CosmosClient cosmosClient = new CosmosClient(endpointUrl, authorizationKey);
Microsoft.Azure.Cosmos.Database database = cosmosClient.GetDatabase(databaseName);
Container container = database.GetContainer(containerName);

An endpointUrl, an authorizationKey…and so on variables are declared in the same place like the cosmosdb_DOCUMENTDB variable. In a Function App configuration section.

After we are connected to Cosmos, we can update the document.

await container.UpsertItemAsync(jsonDocument, new Microsoft.Azure.Cosmos.PartitionKey(jsonDocument.County));

One additional info. If you monitor and do insert/update operations in the same container. You need to create additional conditions/constraints for documents you process. It can be a property checking, like in this example, here is a condition whether an OperationType property is filled in. But you can add an additional field containing a value like “updated”.

if (jsonDocument.OperationType is null)
{
...
}
else
{
log.LogInformation("Nothing to do.");
}

If you don’t do this, then your function falls into an infinite loop. Because the app monitors all inserts/updates. So, when the app saves a document, then it detects the changed document, then grab it and process again…and again.

Here is a code file on GitHub

--

--

Michal Molka

Architect | Azure | Power BI | Fabric | Power Platform | Infrastructure | Security | M365