Querying Cosmos Containers Containing Items of Various Types
Querying Cosmos Containers Containing Items of Various Types
Problem
When querying Cosmos DB from C# code it’s convenient to use Linq. When you only want to get documents
of one type, this is straightforward using GetItemLinqQueryable<T>()
.
When you want to get documents of multiple types, each having different properties but you want to restrict the returned documents by a condition that spans fields that are not common to all of these types, you’re in trouble.
Solution
It turns out that you can get the SQL query text by using something like
string queryText = client.GetContainer("...", "...")
.GetItemLinqQueryable<SomeType>()
.ToQueryDefinition().QueryText;
This will produce a query like:
SELECT VALUE root
FROM root
Notice the SELECT VALUE root
. This means that the data returned is not projected into something
that just has the properties of SomeType
. Instead, the entire documents stored in cosmos DB are
returned.
We can take advantage of this by giving SomeType
all the properties we need for the query’s WHERE
clause. This might be a union of properties of documents that have different types and hence different
properties.
Example
Given the documents of types
record A(string OnlyInA, string FullyQualifiedType);
record B(string OnlyInB, string FullyQualifiedType);
and a type that has the union of properties we’d like to use in the query’s WHERE
clause like
record WhatWeNeedFromAorB (string? OnlyInA, string? OnlyInB);
We can write a query like
string queryText = client.GetContainer("...", "...")
.GetItemLinqQueryable<WhatWeNeedFromAorB>()
.Where(o => o.OnlyInA == "foo" || o.OnlyInB == "bar")
.ToQueryDefinition().QueryText;
The resulting query will be
SELECT VALUE root
FROM root
WHERE root["OnlyInA"] = "foo" OR root["OnlyInB"] = "bar"
We can then pass queryText
to GetItemQueryIterator<JObject>()
to get all the matching documents.
While iterating through them, we can then deserialize each document to the appropriate type:
FeedIterator<JObject> iterator = await client
.GetContainer("...", "...")
.GetItemQueryIterator<JObject>(queryText);
while (iterator.HasMoreResults)
{
foreach (JObject document in await query.ReadNextAsync())
{
string fullyQualifiedType = document["FullyQualifiedType"].Value<string>();
Type type = Type.GetType(fullyQualifiedType);
yield return document.ToObject(type);
}
}
Hire me! Web frontend with JavaScript, TypeScript, React, Svelte, HTML, CSS. Backend with .Net/C#, Node.js, (MS-)SQL. I fill your resource gaps, take on whole projects and create prototypes. Yes, tell me more