Reputation: 377
I'm using .NET Core 3 to read from device twins in an Azure IoT hub. I want to get property X and that property is, as always, both stored in the desired and reported properties. I want to get the one that's newer. This information is written in the metadata.
My question is, is this possible via just the IoT Hub Query Language or do I have to fetch from both desired and reported and check this out myself?
Upvotes: 0
Views: 697
Reputation: 8235
The Azure IoT Hub Query Language supports only the subset of the SQL statements, so the following example (device1 and twin property color) shows a workaround for missing a CASE statement:
query string to get the desired property as the lastUpdated:
querystring = $"SELECT devices.properties.desired.color FROM devices WHERE deviceId = 'device1' and devices.properties.desired.$metadata.color.$lastUpdated > devices.properties.reported.$metadata.color.$lastUpdated";
if the return value is empty, we have to make the second query to obtain a reported property such as:
querystring = $"SELECT devices.properties.reported.color FROM devices WHERE deviceId = 'device1' and devices.properties.reported.$metadata.color.$lastUpdated > devices.properties.desired.$metadata.color.$lastUpdated";
if the return value is still empty, there are missing our desired and/or reported property in the device twin or the deviceId is wrong.
The following code snippet shows an example of the above usage:
using Microsoft.Azure.Devices;
using System.Linq;
using System;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
static string connectionString = "*****";
static async Task Main(string[] args)
{
RegistryManager registryManager = RegistryManager.CreateFromConnectionString(connectionString);
string deviceId = "device1";
string propertyName = "color";
string querystring = $"SELECT devices.properties.desired.{propertyName} FROM devices WHERE deviceId = '{deviceId}' and devices.properties.desired.$metadata.{propertyName}.$lastUpdated > devices.properties.reported.$metadata.{propertyName}.$lastUpdated";
dynamic prop = null;
for (int ii = 0; ii < 2; ii++)
{
var query = registryManager.CreateQuery(querystring);
{
prop = (await query.GetNextAsJsonAsync())?.FirstOrDefault();
if (prop == null)
querystring = $"SELECT devices.properties.reported.{propertyName} FROM devices WHERE deviceId = '{deviceId}' and devices.properties.reported.$metadata.{propertyName}.$lastUpdated > devices.properties.desired.$metadata.{propertyName}.$lastUpdated";
else
break;
}
}
Console.WriteLine(prop ?? $"Not found property '{propertyName}' or device '{deviceId}'");
}
}
}
UPDATE:
In the case of multiple properties, we have to check each property individually by code in the fetched device twin entity. The following code snippet shows an example of this checking:
// multiple properties
querystring = $"SELECT devices.properties FROM devices WHERE deviceId='{deviceId}'";
var query2 = registryManager.CreateQuery(querystring);
JObject prop2 = JObject.Parse((await query2.GetNextAsJsonAsync())?.FirstOrDefault());
JToken desired = prop2.SelectToken("properties.desired");
JToken reported = prop2.SelectToken("properties.reported");
string pathLastUpdated = $"$metadata.{propertyName}.$lastUpdated";
var color = (DateTime)desired.SelectToken(pathLastUpdated) > (DateTime)reported.SelectToken(pathLastUpdated) ?
(string)desired[propertyName] : (string)reported[propertyName];
// more properties
Console.WriteLine(color);
also, you can create an extension class to simplify the code, see the following example:
public static class JObjectExtensions
{
public static T GetLastUpdated<T>(this JObject properties, string propertyName)
{
JToken desired = properties.SelectToken("properties.desired");
JToken reported = properties.SelectToken("properties.reported");
string pathLastUpdated = $"$metadata.{propertyName}.$lastUpdated";
return (DateTime)desired.SelectToken(pathLastUpdated) > (DateTime)reported.SelectToken(pathLastUpdated) ?
desired.SelectToken(propertyName).ToObject<T>() : reported.SelectToken(propertyName).ToObject<T>();
}
public static string GetLastUpdated(this JObject properties, string propertyName)
{
return GetLastUpdated<string>(properties, propertyName);
}
}
the following usage of the above extension shows how can be obtained any desired vs reported properties based on their lastUpdated timestamp:
color = prop2.GetLastUpdated(propertyName);
string color2 = prop2.GetLastUpdated("test.color");
var test = prop2.GetLastUpdated<JObject>("test");
string jsontext = prop2.GetLastUpdated<JObject>("test").ToString(Formatting.None);
var test2 = prop2.GetLastUpdated<Test>("test");
int counter = prop2.GetLastUpdated<int>("counter");
Note, that the exception is thrown in the case of property missing.
Upvotes: 1