Digital Twins Query Builder

Published Jun 20 2022 02:03 PM 1,480 Views


Azure Digital Twins (ADT) provides the capability to model physical entities in a graph-like digital representation and interact with the digital entities in various ways. It has a Query API that can be used to retrieve data using complex queries and traverses the graph representation in an efficient way. The query language supported by the Query API has a SQL-like syntax and a wide range of graph-traversal capabilities.

In this article, we give a quick overview of the Query Builder, a powerful tool developed by the Digital Workplace team to facilitate building complex queries without needing to memorize the query syntax and getting compile-time validation for the queries. To learn more about Query Builder, please refer to the Query Builder documentation.

For a primer on ADT models and twins to help in understanding these queries, check (DTDL models - Azure Digital Twins | Microsoft Docs) and (Digital twins and the twin graph - Azure Digital Twins | Microsoft Docs)


Sample Queries


Get Building by name


SELECT building
WHERE IS_OF_MODEL(‘building’, ‘dtmi:contoso:Building;1’) AND building.Name = ‘Millennium E’


Here, we are giving the DIGITALTWINS collection an alias named building to use in the query clauses. The WHERE clause contains two filters: one that filters the model to only include twins of the Building model, and another one that puts a condition on the Name property of the twin.

This query can be built using the query builder just as follows:


     .Where<Building>(b => b.Name, ComparisonOperators.IsEqualTo, "Millennium E")


Queries can be more complex. For example, if we want to get the second floor of Millennium D building, the query will look like the following:


SELECT floor
JOIN floor RELATED building.hasFloor
WHERE IS_OF_MODEL(‘building’, ‘dtmi:contoso:Building;1’) 
    AND IS_OF_MODEL(‘floor, ‘dtmi:contoso:Floor;1’) 
    AND building.Name = ‘Millennium D’ 
    AND floor.level = 2


Here we needed to not only scope our result to Millennium D, but we had to traverse the relationship between the building and its floors and then filter the floor by the level property to get the right floor.

This one can be built as follows:


      .Join<Building, Floor>(b => b.HasFloor)
      .Where<Building>(b => b.Name, ComparisonOperators.IsEqualTo, "Millennium D")
      .Where<Floor>(f => f.Level, ComparisonOperators.IsEqualTo, 2)


This is a brief overview of the basic usage of the query builder. For more information and examples, visit the repository and read the documentation.




The samples above show how queries can be built using Query Builder. But if the result is the same, why bother and use another library to add another layer of abstraction? What value do we get from using Query Builder? Here is a brief overview of some of the benefits without going too much into detail.


IntelliSense support

Query Builder assumes that models are represented as C# classes, with the relevant properties and relationships. This enables users to select properties and relationships from the enclosing model without the need to remember the exact name – IntelliSense will list all of them as the model is specified in the generic type. This reduces the chance of typing errors as the compiler is involved in catching these mistakes before they’re sent to ADT API.


Built-in compile-time validation

Query Builder is designed to guide the users through each allowed flow by constraining the applicability of certain methods in invalid scenarios. For example, it won’t allow you to SELECT COUNT() and SELECT a property at the same time. As you add more to the query, your next available options are reduced.


Type inference

The BuildAdtQuery() method converts the query object to a string, but the query object itself has some significant value. The second query sample provided above produces a query object of type Query<Floor>. It has the type of the expected twins embedded into it. So with some smart handling of the ADT client call, you can de-serialize the response to the right twin type directly without needing to parse the API response and figure out how to properly process it.

1 Comment
Version history
Last update:
‎Jun 21 2022 08:21 AM