{
"$type": "site.standard.document",
"canonicalUrl": "https://johnnyreilly.com/posts/azure-cosmosdb-container-item-generics",
"description": "Learn how to use generics to store and retrieve different types of object in an Azure Cosmos DB Container. And how to deserialize the data property into a C# object of a specific type.",
"path": "/posts/azure-cosmosdb-container-item-generics",
"publishedAt": "2024-04-01T00:00:00.000Z",
"site": "at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.publication/3mova7c4nho2b",
"tags": [
"azure",
"c#"
],
"textContent": "Cosmos DB is a great database for storing objects. But what if you want to store subtly different types of object in the same container? This post demonstrates how you can use generics to store and retrieve different types of object in an Azure Cosmos DB Container using C#.\n\n\n\nThe problem\n\nThe situation I have in mind isn't entirely different types of object. Rather, it's a standard type of object with a single property that can be of different types. Consider the following record:\n\nThe data property is a JSON object that can be of any shape. In this case, it's a car with four wheels and a blue colour. But it could just as easily be a house with a number of rooms and a garden. Or a person with a name and an age. Or a book with a title and an author. You get the idea.\n\nHow can we store and retrieve these objects in a Cosmos DB container with C#?\n\nA generic solution\n\nThe answer is to use generics. Here's the MyItem record that we're using in the above code:\n\nThe MyItem record is a generic record with a single type parameter TData. The first record is a convenience record that uses object as the type parameter. This is the record that we'll use when we're writing a record that does not have a data property, or when we're reading a record and we don't initially care about the data property.\n\nThe type field represents the type of data. This is a string that can be used to distinguish between different types of object. In the example above, the type is \"car\". In other examples, the type might be \"house\", \"person\", or \"book\". We can use this in future to filter the data by type and to deserialize the data property into the correct Ctype; for instance Car. We just need to know how the type string maps to a particular Ctype.\n\nWriting to and reading from the Cosmos DB container\n\nIn the DatabaseMyItemService class, we have methods to write to and read from the Cosmos DB container:\n\nYou'll note that the UpsertItem and GetItem methods are generic methods that take and return a MyItem<TData> record respectively. The GetItems method is not generic because it returns a list of MyItem records, which are the non-generic records; where data is of type object?.\n\nImagine, you might use the GetItems method to get all the items. If you wanted to load a particular item, in a strongly typed fashion, you might subsequently use the GetItem method to load a single item with a particular type, like so:\n\nDeserializing the data property with JSON.NET\n\nIf you want to avoid requerying the database to get the object in strongly typed form, you'll need to convert the data property into a Cobject of a specific type. If you've retrieved the non-generic MyItem from Cosmos, as far as Cis concerned, the data property is just an object? at this point. Well, that's not quite true. It's actually a JObject from the Newtonsoft.Json library. (This is because the Cosmos DB SDK uses Newtonsoft.Json internally.)\n\nYou can use JObject.ToObject<T>() to convert the data property into a Cobject of a specific type. Here's an example of how you might do this:\n\nDeserializing the data property with System.Text.Json\n\nYou may well find yourself wanting to send a list of items to the front end. However, because the default serializer of ASP.NET is System.Text.Json.JsonSerializer you'll need a different approach to deal with the JObject, as you can't send a JObject to the front end. You need to deserialize it into a format that can be sent to the front end.\n\nIt's quite typical to have a method that converts a domain model to a view model; something like this:\n\nHere's an example of how you might convert our domain model to our view model. It includes a mechanism that uses System.Text.Json.JsonSerializer to deserialize the data property into an object? that can be sent to the front end:\n\nConclusion\n\nIn this post, we've seen how you can use generics to store and retrieve different types of object in an Azure Cosmos DB Container using C#. We've seen how you can use a generic record to store objects with a single property that can be of different types. We've also seen how you can use Newtonsoft.Json to deserialize the data property into a Cobject of a specific type. And we've seen how you can use System.Text.Json to deserialize the data property into an object? that can be sent to the front end.",
"title": "Azure Cosmos DB: container items and generics"
}