Tuesday, 1 August 2023

Writing and reading unmapped properties in Azure Cosmos DB

This article presents code that shows how you can read and write unmapped properties in Azure Cosmos DB. An unmapped property in Azure Cosmos is a property that is NOT part of a model in your domain models, for example you could set a LastUpdateBy or other metadata and not expose this in your domain models. This is similar to shadow properties in Entity Framework, but these do exist in your domain models and are ignored actively using
Fluent API when configuring your db context. In Azure Cosmos, the RAW json is exposed in the "__json" property. This can be read using Newtonsoft library and you can add properties that are unmapped and read them afterwards. You should not only save the changes to persist them in Azure Cosmos but also re-read the data again to get an updated item in your container (table) in Azure Cosmos DB. The following extension methods allows to write and read such unmapped properties easier using Azure Cosmos DB.


public static class AzureCosmosEntityExtensions
{

    public static TResult? GetUnmappedProperty<TResult, T>(this T entity, string propname, DbContext context) where T : class {
        if (entity == null)
        {
            return default(TResult);
        }
        var entry = context.Entry(entity);
        var rawJson = entry.Property<JObject>("__jObject");
        var currentValueProp = rawJson.CurrentValue[propname];
        if (currentValueProp == null)
        {
            return default(TResult);
        }
        var currentValuePropCasted = currentValueProp.ToObject<TResult?>();
        return currentValuePropCasted;
    }

    public static void SetUnmappedProperty<T>(this T entity, string propname, object value, DbContext context) where T : class
    {
        if (entity == null)
        {
            return;
        }
        var entry = context.Entry(entity);
        var rawJson = entry.Property<JObject>("__jObject");
        rawJson.CurrentValue[propname] = JToken.FromObject(value);
        entry.State = EntityState.Modified;
    }

}


Let's see some sample code to set this up. Consider the following model :


 public class Address
  {
    public string AddressId { get; set; }
    public string State { get; set; }
    public string City { get; set; }
    public string Street { get; set; }
    public string HouseNumber { get; set; }
  }


Let's add another unmapped property "LastUpdate" that are not exposed in the domain model, but is an unmapped model. We must as mentioned make sure to reload the data we read here after saving the entity to test out reading the unmapped property. Note that we set ModelState to Modified to trigger the saving of these unmapped properties, since the ChangeTracker is not tracking them and EF will not save the updates to these unmapped properties if this is not done.


     //Create a db context and for an item entity in a container (table) in Azure Cosmos DB,
     //set the unmapped property to a value and also read out this property after saving it and reloading the entity

     using var context = await contextFactory.CreateDbContextAsync();

     var address = await context.Addresses.FirstAsync();
     const string unmappedPropKey = "LastUpdate";
     address.SetUnmappedProperty(unmappedPropKey, DateTime.UtcNow, context);
     await context.SaveChangesAsync();
     address = await context.Addresses.FirstAsync();
     var unnmappedProp = address.GetUnmappedProperty<DateTime, Address>(unmappedPropKey, context);


The following screenshot below shows the unmapped property LastUpdate was written to Azure Cosmos DB item in the container (table).

No comments:

Post a Comment