// </FetchAndPrintMethod>

        // <FindOutgoingRelationshipsMethod>
        private static async Task <List <BasicRelationship> > CustomMethod_FindOutgoingRelationshipsAsync(DigitalTwinsClient client, string dtId)
        {
            // Find the relationships for the twin

            try
            {
                // GetRelationshipsAsync will throw if an error occurs
                // <GetRelationshipsCall>
                AsyncPageable <BasicRelationship> rels = client.GetRelationshipsAsync <BasicRelationship>(dtId);
                // </GetRelationshipsCall>
                var results = new List <BasicRelationship>();
                await foreach (BasicRelationship rel in rels)
                {
                    results.Add(rel);
                    Console.WriteLine($"Found relationship: {rel.Id}");

                    //Print its properties
                    Console.WriteLine($"Relationship properties:");
                    foreach (KeyValuePair <string, object> property in rel.Properties)
                    {
                        Console.WriteLine("{0} = {1}", property.Key, property.Value);
                    }
                }

                return(results);
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"*** Error {ex.Status}/{ex.ErrorCode} retrieving relationships for {dtId} due to {ex.Message}");
                return(null);
            }
        }
        // </FetchAndPrintMethod>

        // <FindOutgoingRelationshipsMethod>
        private static async Task <List <BasicRelationship> > FindOutgoingRelationshipsAsync(DigitalTwinsClient client, string dtId)
        {
            // Find the relationships for the twin

            try
            {
                // GetRelationshipsAsync will throw if an error occurs
                // <GetRelationshipsCall>
                AsyncPageable <BasicRelationship> rels = client.GetRelationshipsAsync <BasicRelationship>(dtId);
                // </GetRelationshipsCall>
                var results = new List <BasicRelationship>();
                await foreach (BasicRelationship rel in rels)
                {
                    results.Add(rel);
                    Console.WriteLine($"Found relationship-{rel.Name}->{rel.TargetId}");
                }

                return(results);
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"*** Error {ex.Status}/{ex.ErrorCode} retrieving relationships for {dtId} due to {ex.Message}");
                return(null);
            }
        }
Exemplo n.º 3
0
        /// <summary>
        /// Delete a twin, and any relationships it might have
        /// </summary>
        public async Task DeleteTwinsAsync()
        {
            PrintHeader("DELETE DIGITAL TWINS");
            Dictionary <string, string> twins = FileHelper.LoadAllFilesInPath(s_twinsPath);

            foreach (KeyValuePair <string, string> twin in twins)
            {
                try
                {
                    // Delete all relationships

                    #region Snippet:DigitalTwinsSampleGetRelationships

                    AsyncPageable <string> relationships = client.GetRelationshipsAsync(twin.Key);

                    #endregion Snippet:DigitalTwinsSampleGetRelationships

                    await foreach (var relationshipJson in relationships)
                    {
                        BasicRelationship relationship = JsonSerializer.Deserialize <BasicRelationship>(relationshipJson);
                        await client.DeleteRelationshipAsync(twin.Key, relationship.Id);

                        Console.WriteLine($"Found and deleted relationship {relationship.Id}");
                    }

                    // Delete any incoming relationships

                    #region Snippet:DigitalTwinsSampleGetIncomingRelationships

                    AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(twin.Key);

                    #endregion Snippet:DigitalTwinsSampleGetIncomingRelationships

                    await foreach (IncomingRelationship incomingRelationship in incomingRelationships)
                    {
                        await client.DeleteRelationshipAsync(incomingRelationship.SourceId, incomingRelationship.RelationshipId);

                        Console.WriteLine($"Found and deleted incoming relationship {incomingRelationship.RelationshipId}");
                    }

                    // Now the digital twin should be safe to delete

                    #region Snippet:DigitalTwinsSampleDeleteTwin

                    await client.DeleteDigitalTwinAsync(twin.Key);

                    #endregion Snippet:DigitalTwinsSampleDeleteTwin

                    Console.WriteLine($"Deleted digital twin {twin.Key}");
                }
                catch (RequestFailedException ex) when(ex.Status == (int)HttpStatusCode.NotFound)
                {
                    // Digital twin or relationship does not exist
                }
                catch (RequestFailedException ex)
                {
                    FatalError($"Failed to delete {twin.Key} due to {ex.Message}");
                }
            }
        }
Exemplo n.º 4
0
        // </Create_relationship>

        // <List_relationships>
        public async static Task ListRelationships(DigitalTwinsClient client, string srcId)
        {
            try {
                AsyncPageable <BasicRelationship> results = client.GetRelationshipsAsync <BasicRelationship>(srcId);
                Console.WriteLine($"Twin {srcId} is connected to:");
                await foreach (BasicRelationship rel in results)
                {
                    Console.WriteLine($" -{rel.Name}->{rel.TargetId}");
                }
            } catch (RequestFailedException rex) {
                Console.WriteLine($"Relationship retrieval error: {rex.Status}:{rex.Message}");
            }
        }
        public static async Task Main(string[] args)
        {
            var adtInstanceUrl = "https://ADT_INSTANCE_HOST_NAME";
            var credential     = new DefaultAzureCredential();
            var client         = new DigitalTwinsClient(new Uri(adtInstanceUrl), credential);

            //retrieve the factory twin - this query will take a moment longer due to the cold start
            var factoryId = "FA44212";
            var factory   = client.GetDigitalTwin <BasicDigitalTwin>(factoryId);

            Console.WriteLine($"Factory {factory.Value.Id} retrieved.");
            foreach (KeyValuePair <string, object> prop in factory.Value.Contents)
            {
                Console.WriteLine($"Factory {factory.Value.Id} has Property: {prop.Key} with value {prop.Value.ToString()}");
            }

            //output factory property metadata, indicating the last time the property was updated
            foreach (var md in factory.Value.Metadata.PropertyMetadata)
            {
                Console.WriteLine($"Factory {factory.Value.Id} Property: {md.Key} was last updated {md.Value.LastUpdatedOn}");
            }

            //retrieve factory relationships
            IAsyncEnumerable <BasicRelationship> relationships = client.GetRelationshipsAsync <BasicRelationship>(factoryId);

            await foreach (BasicRelationship relationship in relationships)
            {
                Console.WriteLine($"Factory {factory.Value.Id} has relationship '{relationship.Name}' with {relationship.TargetId}");
            }

            //retrieve only the Efficiency property value for the factory using a projection
            var projectionQuery1 = $"SELECT Factory.Efficiency FROM DIGITALTWINS Factory WHERE Factory.$dtId='{factoryId}'";
            IAsyncEnumerable <JsonElement> response = client.QueryAsync <JsonElement>(projectionQuery1);

            await foreach (JsonElement ret in response)
            {
                Console.WriteLine($"{factoryId} Efficiency is at {ret.GetProperty("Efficiency")}");
            }

            //retrieve factories with Efficiency equal to or over 90
            var queryByPropertyValue = $"SELECT Factory FROM DIGITALTWINS Factory WHERE Factory.Efficiency >= 90";
            IAsyncEnumerable <BasicDigitalTwin> response2 = client.QueryAsync <IDictionary <string, BasicDigitalTwin> >(queryByPropertyValue).Select(_ => _["Factory"]);

            await foreach (BasicDigitalTwin ret in response2)
            {
                Console.WriteLine($"Factory {ret.Id} has an Efficiency over 90.");
            }
        }
Exemplo n.º 6
0
        private static async Task PropagateStateToAzureMapsAsync(string capabilityTwinId, string capabilityName, JToken capabilityValue, ILogger log)
        {
            // Find parent to which the capability applies, using isCapabilityOf relationships
            List <string> parentTwinIds            = new List <string>();
            AsyncPageable <BasicRelationship> rels = dtClient.GetRelationshipsAsync <BasicRelationship>(capabilityTwinId, "isCapabilityOf");

            await foreach (BasicRelationship relationship in rels)
            {
                parentTwinIds.Add(relationship.TargetId);
            }
            if (parentTwinIds.Count != 1)
            {
                log.LogInformation($"Capability twin {capabilityTwinId} isn't assigned to exactly one parent.");
                return;
            }
            string parentTwinId = parentTwinIds.First();
            Response <BasicDigitalTwin> parentTwinResponse = await dtClient.GetDigitalTwinAsync <BasicDigitalTwin>(parentTwinId);

            BasicDigitalTwin parentTwin = parentTwinResponse.Value;

            // TODO: Based on parent twin, get Azure Maps Feature ID (either from ADT model using externalIds property, or by querying Azure Maps feature service )
            string AzureMapsFeatureID = "NONEXISTENT_FIXME";

            // Load the Azure Maps stateset ID (configured in an env. variable JSON fragment) that corresponds to this capability name
            if (!azureMapsStateSets.ContainsKey(capabilityName))
            {
                log.LogInformation($"There is no configured Azure Maps stateset supporting the capability type {capabilityName}.");
                return;
            }
            string statesetID = azureMapsStateSets[capabilityName].ToString();

            // Update the maps feature stateset
            JObject postcontent = new JObject(
                new JProperty(
                    "States",
                    new JArray(
                        new JObject(
                            new JProperty("keyName", capabilityName),
                            new JProperty("value", capabilityValue.ToString()),
                            new JProperty("eventTimestamp", DateTime.UtcNow.ToString("s"))))));

            var response = await httpClient.PostAsync(
                $"https://us.atlas.microsoft.com/featureState/state?api-version=1.0&statesetID={statesetID}&featureID={AzureMapsFeatureID}&subscription-key={azureMapsSubscriptionKey}",
                new StringContent(postcontent.ToString()));

            log.LogInformation($"Azure Maps response:\n\t" + await response.Content.ReadAsStringAsync());
        }
Exemplo n.º 7
0
        public static async Task DeleteRelationships(DigitalTwinsClient client, string dtId, ILogger log)
        {
            var relationshipIds = new List <string>();

            AsyncPageable <string> relationships = client.GetRelationshipsAsync(dtId);

            await foreach (var relationshipJson in relationships)
            {
                BasicRelationship relationship = System.Text.Json.JsonSerializer.Deserialize <BasicRelationship>(relationshipJson);
                relationshipIds.Add(relationship.Id);
            }

            foreach (var relationshipId in relationshipIds)
            {
                client.DeleteRelationship(dtId, relationshipId);
                log.LogInformation($"Twin '{dtId}' relationship '{relationshipId}' deleted in DT");
            }
        }
Exemplo n.º 8
0
        /// <summary>
        /// Deletes all outgoing and incoming relationships from a specified digital twin
        /// </summary>
        public static async Task DeleteAllRelationshipsAsync(DigitalTwinsClient client, string dtId, ILogger log)
        {
            AsyncPageable <BasicRelationship> relationships = client.GetRelationshipsAsync <BasicRelationship>(dtId);

            await foreach (BasicRelationship relationship in relationships)
            {
                await client.DeleteRelationshipAsync(dtId, relationship.Id, relationship.ETag);

                log.LogInformation($"Twin {dtId} relationship {relationship.Id} deleted in DT");
            }

            AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(dtId);

            await foreach (IncomingRelationship incomingRelationship in incomingRelationships)
            {
                await client.DeleteRelationshipAsync(incomingRelationship.SourceId, incomingRelationship.RelationshipId);

                log.LogInformation($"Twin {dtId} incoming relationship {incomingRelationship.RelationshipId} from {incomingRelationship.SourceId} deleted in DT");
            }
        }
        private static async Task FindAndDeleteOutgoingRelationshipsAsync(DigitalTwinsClient client, string dtId)
        {
            // Find the relationships for the twin

            try
            {
                // GetRelationshipsAsync will throw an error if a problem occurs
                AsyncPageable <BasicRelationship> rels = client.GetRelationshipsAsync <BasicRelationship>(dtId);

                await foreach (BasicRelationship rel in rels)
                {
                    await client.DeleteRelationshipAsync(dtId, rel.Id).ConfigureAwait(false);

                    Console.WriteLine($"Deleted relationship {rel.Id} from {dtId}");
                }
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"*** Error {ex.Status}/{ex.ErrorCode} retrieving or deleting relationships for {dtId} due to {ex.Message}");
            }
        }
Exemplo n.º 10
0
        public async Task FindAndDeleteOutgoingRelationshipsAsync(string dtId)
        {
            // Find the relationships for the twin

            try
            {
                // GetRelationshipsAsync will throw if an error occurs
                AsyncPageable <string> relsJson = client.GetRelationshipsAsync(dtId);

                await foreach (string relJson in relsJson)
                {
                    var rel = System.Text.Json.JsonSerializer.Deserialize <BasicRelationship>(relJson);
                    await client.DeleteRelationshipAsync(dtId, rel.Id).ConfigureAwait(false);

                    Console.WriteLine($"Deleted relationship {rel.Id} from {dtId}");
                }
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"*** Error {ex.Status}/{ex.ErrorCode} retrieving or deleting relationships for {dtId} due to {ex.Message}");
            }
        }
        public static async Task GetRelationships(DigitalTwinsClient client)
        {
            Console.Write("source twinId: ");
            string sourceTwinId = Console.ReadLine();

            try
            {
                AsyncPageable <string> list = client.GetRelationshipsAsync(sourceTwinId);

                await foreach (string s in list)
                {
                    BasicRelationship rel = JsonSerializer.Deserialize <BasicRelationship>(s);
                    Console.WriteLine("id={0}, name={1}", rel.Id, rel.Name);
                }
            }
            catch (RequestFailedException e)
            {
                Console.WriteLine($"Error {e.Status}: {e.Message}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex}");
            }
        }
Exemplo n.º 12
0
        /// <summary>
        /// Creates two digital twins, and connect them with relationships.
        /// </summary>
        public async Task RunSamplesAsync(DigitalTwinsClient client)
        {
            // For the purpose of keeping code snippets readable to the user, hardcoded string literals are used in place of assigned variables, eg Ids.
            // Despite not being a good code practice, this prevents code snippets from being out of context for the user when making API calls that accept Ids as parameters.

            PrintHeader("RELATIONSHIP SAMPLE");
            string sampleBuildingModelId = "dtmi:com:samples:SampleBuilding;1";
            string sampleFloorModelId    = "dtmi:com:samples:SampleFloor;1";
            await ModelLifecycleSamples.TryDeleteModelAsync(client, sampleBuildingModelId);

            await ModelLifecycleSamples.TryDeleteModelAsync(client, sampleFloorModelId);

            // Create a building digital twin model.
            string buildingModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload
                                          .Replace(SamplesConstants.RelationshipTargetModelId, sampleFloorModelId)
                                          .Replace(SamplesConstants.ModelId, sampleBuildingModelId)
                                          .Replace(SamplesConstants.ModelDisplayName, "Building")
                                          .Replace(SamplesConstants.RelationshipName, "contains");

            await client.CreateModelsAsync(
                new[]
            {
                buildingModelPayload
            });

            Console.WriteLine($"Created model '{sampleBuildingModelId}'.");

            // Create a floor digital twin model.
            string floorModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload
                                       .Replace(SamplesConstants.RelationshipTargetModelId, sampleBuildingModelId)
                                       .Replace(SamplesConstants.ModelId, sampleFloorModelId)
                                       .Replace(SamplesConstants.ModelDisplayName, "Floor")
                                       .Replace(SamplesConstants.RelationshipName, "containedIn");

            await client.CreateModelsAsync(new[] { floorModelPayload });

            // Get the model we just created
            Response <DigitalTwinsModelData> getFloorModelResponse = await client.GetModelAsync(sampleFloorModelId).ConfigureAwait(false);

            Console.WriteLine($"Created model '{getFloorModelResponse.Value.Id}'");

            // Create a building digital twin.
            var buildingDigitalTwin = new BasicDigitalTwin
            {
                Id       = "buildingTwinId",
                Metadata = { ModelId = sampleBuildingModelId }
            };

            Response <BasicDigitalTwin> createDigitalTwinResponse = await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>("buildingTwinId", buildingDigitalTwin);

            Console.WriteLine($"Created twin '{createDigitalTwinResponse.Value.Id}'.");

            // Create a floor digital.
            var floorDigitalTwin = new BasicDigitalTwin
            {
                Id       = "floorTwinId",
                Metadata = { ModelId = sampleFloorModelId }
            };

            Response <BasicDigitalTwin> createFloorDigitalTwinResponse = await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>("floorTwinId", floorDigitalTwin);

            Console.WriteLine($"Created twin '{createFloorDigitalTwinResponse.Value.Id}'.");

            // Create a relationship between building and floor using the BasicRelationship serialization helper.

            #region Snippet:DigitalTwinsSampleCreateBasicRelationship

            var buildingFloorRelationshipPayload = new BasicRelationship
            {
                Id         = "buildingFloorRelationshipId",
                SourceId   = "buildingTwinId",
                TargetId   = "floorTwinId",
                Name       = "contains",
                Properties =
                {
                    { "Prop1", "Prop1 value" },
                    { "Prop2",             6 }
                }
            };

            Response <BasicRelationship> createBuildingFloorRelationshipResponse = await client
                                                                                   .CreateOrReplaceRelationshipAsync <BasicRelationship>("buildingTwinId", "buildingFloorRelationshipId", buildingFloorRelationshipPayload);

            Console.WriteLine($"Created a digital twin relationship '{createBuildingFloorRelationshipResponse.Value.Id}' " +
                              $"from twin '{createBuildingFloorRelationshipResponse.Value.SourceId}' to twin '{createBuildingFloorRelationshipResponse.Value.TargetId}'.");

            #endregion Snippet:DigitalTwinsSampleCreateBasicRelationship

            // You can get a relationship as a BasicRelationship type.

            #region Snippet:DigitalTwinsSampleGetBasicRelationship

            Response <BasicRelationship> getBasicRelationshipResponse = await client.GetRelationshipAsync <BasicRelationship>(
                "buildingTwinId",
                "buildingFloorRelationshipId");

            if (getBasicRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK)
            {
                BasicRelationship basicRelationship = getBasicRelationshipResponse.Value;
                Console.WriteLine($"Retrieved relationship '{basicRelationship.Id}' from twin {basicRelationship.SourceId}.\n\t" +
                                  $"Prop1: {basicRelationship.Properties["Prop1"]}\n\t" +
                                  $"Prop2: {basicRelationship.Properties["Prop2"]}");
            }

            #endregion Snippet:DigitalTwinsSampleGetBasicRelationship

            // Alternatively, you can create your own custom data types and use these types when calling into the APIs.
            // This requires less code or knowledge of the type for interaction.

            // Create a relationship between floorTwinId and buildingTwinId using a custom data type.

            #region Snippet:DigitalTwinsSampleCreateCustomRelationship

            var floorBuildingRelationshipPayload = new CustomRelationship
            {
                Id       = "floorBuildingRelationshipId",
                SourceId = "floorTwinId",
                TargetId = "buildingTwinId",
                Name     = "containedIn",
                Prop1    = "Prop1 val",
                Prop2    = 4
            };

            Response <CustomRelationship> createCustomRelationshipResponse = await client
                                                                             .CreateOrReplaceRelationshipAsync <CustomRelationship>("floorTwinId", "floorBuildingRelationshipId", floorBuildingRelationshipPayload);

            Console.WriteLine($"Created a digital twin relationship '{createCustomRelationshipResponse.Value.Id}' " +
                              $"from twin '{createCustomRelationshipResponse.Value.SourceId}' to twin '{createCustomRelationshipResponse.Value.TargetId}'.");

            #endregion Snippet:DigitalTwinsSampleCreateCustomRelationship

            // Getting a relationship as a custom data type is extremely easy.

            #region Snippet:DigitalTwinsSampleGetCustomRelationship

            Response <CustomRelationship> getCustomRelationshipResponse = await client.GetRelationshipAsync <CustomRelationship>(
                "floorTwinId",
                "floorBuildingRelationshipId");

            CustomRelationship getCustomRelationship = getCustomRelationshipResponse.Value;
            Console.WriteLine($"Retrieved and deserialized relationship '{getCustomRelationship.Id}' from twin '{getCustomRelationship.SourceId}'.\n\t" +
                              $"Prop1: {getCustomRelationship.Prop1}\n\t" +
                              $"Prop2: {getCustomRelationship.Prop2}");

            #endregion Snippet:DigitalTwinsSampleGetCustomRelationship

            // Get all relationships in the graph where buildingTwinId is the source of the relationship.

            #region Snippet:DigitalTwinsSampleGetAllRelationships

            AsyncPageable <BasicRelationship> relationships = client.GetRelationshipsAsync <BasicRelationship>("buildingTwinId");
            await foreach (BasicRelationship relationship in relationships)
            {
                Console.WriteLine($"Retrieved relationship '{relationship.Id}' with source {relationship.SourceId}' and " +
                                  $"target {relationship.TargetId}.\n\t" +
                                  $"Prop1: {relationship.Properties["Prop1"]}\n\t" +
                                  $"Prop2: {relationship.Properties["Prop2"]}");
            }

            #endregion Snippet:DigitalTwinsSampleGetAllRelationships

            // Get all incoming relationships in the graph where buildingTwinId is the target of the relationship.

            #region Snippet:DigitalTwinsSampleGetIncomingRelationships

            AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync("buildingTwinId");

            await foreach (IncomingRelationship incomingRelationship in incomingRelationships)
            {
                Console.WriteLine($"Found an incoming relationship '{incomingRelationship.RelationshipId}' from '{incomingRelationship.SourceId}'.");
            }

            #endregion Snippet:DigitalTwinsSampleGetIncomingRelationships

            // Delete the contains relationship, created earlier in the sample code, from building to floor.

            #region Snippet:DigitalTwinsSampleDeleteRelationship

            await client.DeleteRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId");

            Console.WriteLine($"Deleted relationship 'buildingFloorRelationshipId'.");

            #endregion Snippet:DigitalTwinsSampleDeleteRelationship

            // Delete the containedIn relationship, created earlier in the sample code, from floor to building.
            await client.DeleteRelationshipAsync("floorTwinId", "floorBuildingRelationshipId");

            Console.WriteLine($"Deleted relationship 'floorBuildingRelationshipId'.");

            // Clean up.
            try
            {
                // Delete all twins
                await client.DeleteDigitalTwinAsync("buildingTwinId");

                await client.DeleteDigitalTwinAsync("floorTwinId");
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"Failed to delete twin due to {ex}.");
            }

            try
            {
                await client.DeleteModelAsync(sampleBuildingModelId);

                await client.DeleteModelAsync(sampleFloorModelId);
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"Failed to delete model due to {ex}.");
            }
        }
Exemplo n.º 13
0
        /// <summary>
        /// Creates two digital twins, and connect them with relationships.
        /// </summary>
        public async Task RunSamplesAsync(DigitalTwinsClient client)
        {
            // For the purpose of keeping code snippets readable to the user, hardcoded string literals are used in place of assigned variables, eg Ids.
            // Despite not being a good code practice, this prevents code snippets from being out of context for the user when making API calls that accept Ids as parameters.

            PrintHeader("RELATIONSHIP SAMPLE");

            // Create a building digital twin model.
            string buildingModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload
                                          .Replace(SamplesConstants.ModelId, "dtmi:samples:SampleBuilding;1")
                                          .Replace(SamplesConstants.ModelDisplayName, "Building")
                                          .Replace(SamplesConstants.RelationshipName, "contains")
                                          .Replace(SamplesConstants.RelationshipTargetModelId, "dtmi:samples:SampleFloor;1");

            Response <IReadOnlyList <Models.ModelData> > createBuildingModelResponse = await client.CreateModelsAsync(
                new[]
            {
                buildingModelPayload
            });

            Console.WriteLine($"Created model with Id dtmi:samples:SampleBuilding;1. Response status: {createBuildingModelResponse.GetRawResponse().Status}.");

            // Create a floor digital twin model.
            string floorModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload
                                       .Replace(SamplesConstants.ModelId, "dtmi:samples:SampleFloor;1")
                                       .Replace(SamplesConstants.ModelDisplayName, "Floor")
                                       .Replace(SamplesConstants.RelationshipName, "containedIn")
                                       .Replace(SamplesConstants.RelationshipTargetModelId, "dtmi:samples:SampleBuilding;1");

            Response <IReadOnlyList <Models.ModelData> > createFloorModelResponse = await client.CreateModelsAsync(
                new[]
            {
                floorModelPayload
            });

            Console.WriteLine($"Created model with Id dtmi:samples:SampleFloor;1. Response status: {createFloorModelResponse.GetRawResponse().Status}.");

            // Create a building digital twin.
            var buildingDigitalTwin = new BasicDigitalTwin
            {
                Id       = "buildingTwinId",
                Metadata = { ModelId = "dtmi:samples:SampleBuilding;1" }
            };

            string            buildingDigitalTwinPayload        = JsonSerializer.Serialize(buildingDigitalTwin);
            Response <string> createBuildingDigitalTwinResponse = await client.CreateDigitalTwinAsync("buildingTwinId", buildingDigitalTwinPayload);

            Console.WriteLine($"Created a digital twin with Id buildingTwinId. Response status: {createBuildingDigitalTwinResponse.GetRawResponse().Status}.");

            // Create a floor digital.
            var floorDigitalTwin = new BasicDigitalTwin
            {
                Id       = "floorTwinId",
                Metadata = { ModelId = "dtmi:samples:SampleFloor;1" }
            };

            string            floorDigitalTwinPayload        = JsonSerializer.Serialize(floorDigitalTwin);
            Response <string> createFloorDigitalTwinResponse = await client.CreateDigitalTwinAsync("floorTwinId", floorDigitalTwinPayload);

            Console.WriteLine($"Created a digital twin with Id floorTwinId. Response status: {createFloorDigitalTwinResponse.GetRawResponse().Status}.");

            // Create a relationship between building and floor using the BasicRelationship serialization helper.
            #region Snippet:DigitalTwinsSampleCreateBasicRelationship

            var buildingFloorRelationshipPayload = new BasicRelationship
            {
                Id               = "buildingFloorRelationshipId",
                SourceId         = "buildingTwinId",
                TargetId         = "floorTwinId",
                Name             = "contains",
                CustomProperties =
                {
                    { "Prop1", "Prop1 value" },
                    { "Prop2",             6 }
                }
            };

            string            serializedRelationship     = JsonSerializer.Serialize(buildingFloorRelationshipPayload);
            Response <string> createRelationshipResponse = await client.CreateRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId", serializedRelationship);

            Console.WriteLine($"Created a digital twin relationship with Id buildingFloorRelationshipId from digital twin with Id buildingTwinId to digital twin with Id floorTwinId. " +
                              $"Response status: {createRelationshipResponse.GetRawResponse().Status}.");

            #endregion Snippet:DigitalTwinsSampleCreateBasicRelationship

            // You can get a relationship and deserialize it into a BasicRelationship.
            #region Snippet:DigitalTwinsSampleGetBasicRelationship

            Response <string> getBasicRelationshipResponse = await client.GetRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId");

            if (getBasicRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK)
            {
                BasicRelationship basicRelationship = JsonSerializer.Deserialize <BasicRelationship>(getBasicRelationshipResponse.Value);
                Console.WriteLine($"Retrieved relationship with Id {basicRelationship.Id} from digital twin with Id {basicRelationship.SourceId}. " +
                                  $"Response status: {getBasicRelationshipResponse.GetRawResponse().Status}.\n\t" +
                                  $"Prop1: {basicRelationship.CustomProperties["Prop1"]}\n\t" +
                                  $"Prop2: {basicRelationship.CustomProperties["Prop2"]}");
            }

            #endregion Snippet:DigitalTwinsSampleGetBasicRelationship

            // Alternatively, you can create your own custom data types to serialize and deserialize your relationships.
            // This requires less code or knowledge of the type for interaction.

            // Create a relationship between floorTwinId and buildingTwinId using a custom data type.
            #region Snippet:DigitalTwinsSampleCreateCustomRelationship

            var floorBuildingRelationshipPayload = new CustomRelationship
            {
                Id       = "floorBuildingRelationshipId",
                SourceId = "floorTwinId",
                TargetId = "buildingTwinId",
                Name     = "containedIn",
                Prop1    = "Prop1 val",
                Prop2    = 4
            };
            string serializedCustomRelationship = JsonSerializer.Serialize(floorBuildingRelationshipPayload);

            Response <string> createCustomRelationshipResponse = await client.CreateRelationshipAsync("floorTwinId", "floorBuildingRelationshipId", serializedCustomRelationship);

            Console.WriteLine($"Created a digital twin relationship with Id floorBuildingRelationshipId from digital twin with Id floorTwinId to digital twin with Id buildingTwinId. " +
                              $"Response status: {createCustomRelationshipResponse.GetRawResponse().Status}.");

            #endregion Snippet:DigitalTwinsSampleCreateCustomRelationship

            // Getting and deserializing a relationship into a custom data type is extremely easy.
            #region Snippet:DigitalTwinsSampleGetCustomRelationship

            Response <string> getCustomRelationshipResponse = await client.GetRelationshipAsync("floorTwinId", "floorBuildingRelationshipId");

            if (getCustomRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK)
            {
                CustomRelationship getCustomRelationship = JsonSerializer.Deserialize <CustomRelationship>(getCustomRelationshipResponse.Value);
                Console.WriteLine($"Retrieved and deserialized relationship with Id {getCustomRelationship.Id} from digital twin with Id {getCustomRelationship.SourceId}. " +
                                  $"Response status: {getCustomRelationshipResponse.GetRawResponse().Status}.\n\t" +
                                  $"Prop1: {getCustomRelationship.Prop1}\n\t" +
                                  $"Prop2: {getCustomRelationship.Prop2}");
            }

            #endregion Snippet:DigitalTwinsSampleGetCustomRelationship

            // Get all relationships in the graph where buildingTwinId is the source of the relationship.
            #region Snippet:DigitalTwinsSampleGetAllRelationships

            AsyncPageable <string> relationships = client.GetRelationshipsAsync("buildingTwinId");

            await foreach (var relationshipJson in relationships)
            {
                BasicRelationship relationship = JsonSerializer.Deserialize <BasicRelationship>(relationshipJson);
                Console.WriteLine($"Found relationship with Id {relationship.Id} with a digital twin source Id {relationship.SourceId} and " +
                                  $"a digital twin target Id {relationship.TargetId}. \n\t " +
                                  $"Prop1: {relationship.CustomProperties["Prop1"]}\n\t" +
                                  $"Prop2: {relationship.CustomProperties["Prop2"]}");
            }

            #endregion Snippet:DigitalTwinsSampleGetAllRelationships

            // Get all incoming relationships in the graph where buildingTwinId is the target of the relationship.
            #region Snippet:DigitalTwinsSampleGetIncomingRelationships

            AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync("buildingTwinId");

            await foreach (IncomingRelationship incomingRelationship in incomingRelationships)
            {
                Console.WriteLine($"Found an incoming relationship with Id {incomingRelationship.RelationshipId} coming from a digital twin with Id {incomingRelationship.SourceId}.");
            }

            #endregion Snippet:DigitalTwinsSampleGetIncomingRelationships

            // Delete the contains relationship, created earlier in the sample code, from building to floor.

            #region Snippet:DigitalTwinsSampleDeleteRelationship

            Response deleteBuildingRelationshipResponse = await client.DeleteRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId");

            Console.WriteLine($"Deleted relationship with Id buildingFloorRelationshipId. Status response: {deleteBuildingRelationshipResponse.Status}.");

            #endregion Snippet:DigitalTwinsSampleDeleteRelationship

            // Delete the containedIn relationship, created earlier in the sample code, from floor to building.
            Response deleteFloorRelationshipResponse = await client.DeleteRelationshipAsync("floorTwinId", "floorBuildingRelationshipId");

            Console.WriteLine($"Deleted relationship with Id floorBuildingRelationshipId. Status response: {deleteFloorRelationshipResponse.Status}.");

            // Clean up.
            try
            {
                // Delete all twins
                await client.DeleteDigitalTwinAsync("buildingTwinId");

                await client.DeleteDigitalTwinAsync("floorTwinId");
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"Failed to delete digital twin due to {ex}.");
            }

            try
            {
                await client.DeleteModelAsync("dtmi:samples:SampleBuilding;1");

                await client.DeleteModelAsync("dtmi:samples:SampleFloor;1");
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"Failed to delete model due to {ex}.");
            }
        }
        public async Task Relationships_Lifecycle()
        {
            // arrange

            DigitalTwinsClient client = GetClient();

            var floorContainsRoomRelationshipId    = "FloorToRoomRelationship";
            var floorCooledByHvacRelationshipId    = "FloorToHvacRelationship";
            var hvacCoolsFloorRelationshipId       = "HvacToFloorRelationship";
            var roomContainedInFloorRelationshipId = "RoomToFloorRelationship";

            string floorModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false);

            string roomModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false);

            string hvacModelId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacModelIdPrefix).ConfigureAwait(false);

            string floorTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.FloorTwinIdPrefix).ConfigureAwait(false);

            string roomTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false);

            string hvacTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacTwinIdPrefix).ConfigureAwait(false);

            try
            {
                // create floor, room and hvac model
                string floorModel = TestAssetsHelper.GetFloorModelPayload(floorModelId, roomModelId, hvacModelId);
                string roomModel  = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId);
                string hvacModel  = TestAssetsHelper.GetHvacModelPayload(hvacModelId, floorModelId);
                await client.CreateModelsAsync(new List <string> {
                    floorModel, roomModel, hvacModel
                }).ConfigureAwait(false);

                // create floor twin
                BasicDigitalTwin floorTwin = TestAssetsHelper.GetFloorTwinPayload(floorModelId);
                await client.CreateDigitalTwinAsync <BasicDigitalTwin>(floorTwinId, floorTwin).ConfigureAwait(false);

                // Create room twin
                BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId);
                await client.CreateDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false);

                // create hvac twin
                BasicDigitalTwin hvacTwin = TestAssetsHelper.GetHvacTwinPayload(hvacModelId);
                await client.CreateDigitalTwinAsync <BasicDigitalTwin>(hvacTwinId, hvacTwin).ConfigureAwait(false);

                BasicRelationship floorContainsRoomPayload                = TestAssetsHelper.GetRelationshipWithPropertyPayload(roomTwinId, ContainsRelationship, "isAccessRestricted", true);
                BasicRelationship floorTwinCoolsRelationshipPayload       = TestAssetsHelper.GetRelationshipPayload(floorTwinId, CoolsRelationship);
                BasicRelationship floorTwinContainedInRelationshipPayload = TestAssetsHelper.GetRelationshipPayload(floorTwinId, ContainedInRelationship);
                BasicRelationship floorCooledByHvacPayload                = TestAssetsHelper.GetRelationshipPayload(hvacTwinId, CooledByRelationship);
                JsonPatchDocument floorContainsRoomUpdatePayload          = new JsonPatchDocument();
                floorContainsRoomUpdatePayload.AppendReplace("/isAccessRestricted", false);

                // CREATE relationships

                // create Relationship from Floor -> Room
                await client
                .CreateRelationshipAsync <BasicRelationship>(
                    floorTwinId,
                    floorContainsRoomRelationshipId,
                    floorContainsRoomPayload)
                .ConfigureAwait(false);

                // create Relationship from Floor -> Hvac
                await client
                .CreateRelationshipAsync <BasicRelationship>(
                    floorTwinId,
                    floorCooledByHvacRelationshipId,
                    floorCooledByHvacPayload)
                .ConfigureAwait(false);

                // create Relationship from Hvac -> Floor
                await client
                .CreateRelationshipAsync <BasicRelationship>(
                    hvacTwinId,
                    hvacCoolsFloorRelationshipId,
                    floorTwinCoolsRelationshipPayload)
                .ConfigureAwait(false);

                // create Relationship from Room -> Floor
                await client
                .CreateRelationshipAsync <BasicRelationship>(
                    roomTwinId,
                    roomContainedInFloorRelationshipId,
                    floorTwinContainedInRelationshipPayload)
                .ConfigureAwait(false);

                // UPDATE relationships

                // create Relationship from Floor -> Room
                await client
                .UpdateRelationshipAsync(
                    floorTwinId,
                    floorContainsRoomRelationshipId,
                    floorContainsRoomUpdatePayload)
                .ConfigureAwait(false);

                // GET relationship
                Response <BasicRelationship> containsRelationshipId = await client
                                                                      .GetRelationshipAsync <BasicRelationship>(
                    floorTwinId,
                    floorContainsRoomRelationshipId)
                                                                      .ConfigureAwait(false);

                // LIST incoming relationships
                AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(floorTwinId);

                int numberOfIncomingRelationshipsToFloor = 0;
                await foreach (IncomingRelationship relationship in incomingRelationships)
                {
                    ++numberOfIncomingRelationshipsToFloor;
                }
                numberOfIncomingRelationshipsToFloor.Should().Be(2, "floor has incoming relationships from room and hvac");

                // LIST relationships
                AsyncPageable <string> floorRelationships = client.GetRelationshipsAsync(floorTwinId);

                int numberOfFloorRelationships = 0;
                await foreach (var relationship in floorRelationships)
                {
                    ++numberOfFloorRelationships;
                }
                numberOfFloorRelationships.Should().Be(2, "floor has an relationship to room and hvac");

                // LIST relationships by name
                AsyncPageable <string> roomTwinRelationships = client
                                                               .GetRelationshipsAsync(
                    roomTwinId,
                    ContainedInRelationship);
                containsRelationshipId.Value.Id.Should().Be(floorContainsRoomRelationshipId);

                int numberOfRelationships = 0;
                await foreach (var relationship in roomTwinRelationships)
                {
                    ++numberOfRelationships;
                }
                numberOfRelationships.Should().Be(1, "room has only one containedIn relationship to floor");

                await client
                .DeleteRelationshipAsync(
                    floorTwinId,
                    floorContainsRoomRelationshipId)
                .ConfigureAwait(false);

                await client
                .DeleteRelationshipAsync(
                    roomTwinId,
                    roomContainedInFloorRelationshipId)
                .ConfigureAwait(false);

                await client
                .DeleteRelationshipAsync(
                    floorTwinId,
                    floorCooledByHvacRelationshipId)
                .ConfigureAwait(false);

                await client
                .DeleteRelationshipAsync(
                    hvacTwinId,
                    hvacCoolsFloorRelationshipId)
                .ConfigureAwait(false);

                Func <Task> act = async() =>
                {
                    await client
                    .GetRelationshipAsync <BasicRelationship>(
                        floorTwinId,
                        floorContainsRoomRelationshipId)
                    .ConfigureAwait(false);
                };
                act.Should().Throw <RequestFailedException>()
                .And.Status.Should().Be((int)HttpStatusCode.NotFound);

                act = async() =>
                {
                    await client
                    .GetRelationshipAsync <BasicRelationship>(
                        roomTwinId,
                        roomContainedInFloorRelationshipId)
                    .ConfigureAwait(false);
                };
                act.Should().Throw <RequestFailedException>().
                And.Status.Should().Be((int)HttpStatusCode.NotFound);

                act = async() =>
                {
                    await client
                    .GetRelationshipAsync <BasicRelationship>(
                        floorTwinId,
                        floorCooledByHvacRelationshipId)
                    .ConfigureAwait(false);
                };
                act.Should().Throw <RequestFailedException>().
                And.Status.Should().Be((int)HttpStatusCode.NotFound);

                act = async() =>
                {
                    await client
                    .GetRelationshipAsync <BasicRelationship>(
                        hvacTwinId,
                        hvacCoolsFloorRelationshipId)
                    .ConfigureAwait(false);
                };
                act.Should().Throw <RequestFailedException>()
                .And.Status.Should().Be((int)HttpStatusCode.NotFound);
            }
            finally
            {
                // clean up
                try
                {
                    await Task
                    .WhenAll(
                        client.DeleteDigitalTwinAsync(floorTwinId),
                        client.DeleteDigitalTwinAsync(roomTwinId),
                        client.DeleteDigitalTwinAsync(hvacTwinId),
                        client.DeleteModelAsync(hvacModelId),
                        client.DeleteModelAsync(floorModelId),
                        client.DeleteModelAsync(roomModelId))
                    .ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    Assert.Fail($"Test clean up failed: {ex.Message}");
                }
            }
        }
        public async Task Relationships_PaginationWorks()
        {
            DigitalTwinsClient client = GetClient();

            string floorModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false);

            string roomModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false);

            string hvacModelId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacModelIdPrefix).ConfigureAwait(false);

            string floorTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.FloorTwinIdPrefix).ConfigureAwait(false);

            string roomTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false);

            string hvacTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacTwinIdPrefix).ConfigureAwait(false);

            try
            {
                // create floor, room and hvac model
                string floorModel = TestAssetsHelper.GetFloorModelPayload(floorModelId, roomModelId, hvacModelId);
                string roomModel  = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId);
                string hvacModel  = TestAssetsHelper.GetHvacModelPayload(hvacModelId, floorModelId);
                await client.CreateModelsAsync(new List <string> {
                    floorModel, roomModel, hvacModel
                }).ConfigureAwait(false);

                // create floor twin
                BasicDigitalTwin floorTwin = TestAssetsHelper.GetFloorTwinPayload(floorModelId);
                await client.CreateDigitalTwinAsync <BasicDigitalTwin>(floorTwinId, floorTwin).ConfigureAwait(false);

                // Create room twin
                BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId);
                await client.CreateDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false);

                BasicRelationship floorContainsRoomPayload = TestAssetsHelper.GetRelationshipWithPropertyPayload(roomTwinId, ContainsRelationship, "isAccessRestricted", true);
                BasicRelationship floorTwinContainedInRelationshipPayload = TestAssetsHelper.GetRelationshipPayload(floorTwinId, ContainedInRelationship);

                // For the sake of test simplicity, we'll just add multiple relationships from the same floor to the same room.
                for (int i = 0; i < bulkRelationshipCount; i++)
                {
                    var floorContainsRoomRelationshipId = $"FloorToRoomRelationship-{GetRandom()}";

                    // create Relationship from Floor -> Room
                    await client
                    .CreateRelationshipAsync <BasicRelationship>(
                        floorTwinId,
                        floorContainsRoomRelationshipId,
                        floorContainsRoomPayload)
                    .ConfigureAwait(false);
                }

                // For the sake of test simplicity, we'll just add multiple relationships from the same room to the same floor.
                for (int i = 0; i < bulkRelationshipCount; i++)
                {
                    var roomContainedInFloorRelationshipId = $"RoomToFloorRelationship-{GetRandom()}";

                    // create Relationship from Room -> Floor
                    await client
                    .CreateRelationshipAsync <BasicRelationship>(
                        roomTwinId,
                        roomContainedInFloorRelationshipId,
                        floorTwinContainedInRelationshipPayload)
                    .ConfigureAwait(false);
                }

                // LIST incoming relationships by page
                AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(floorTwinId);

                int incomingRelationshipPageCount = 0;
                await foreach (Page <IncomingRelationship> incomingRelationshipPage in incomingRelationships.AsPages())
                {
                    incomingRelationshipPageCount++;
                    if (incomingRelationshipPage.ContinuationToken != null)
                    {
                        incomingRelationshipPage.Values.Count.Should().Be(defaultRelationshipPageSize, "Unexpected page size for a non-terminal page");
                    }
                }

                incomingRelationshipPageCount.Should().BeGreaterThan(1, "Expected more than one page of incoming relationships");

                // LIST outgoing relationships by page
                AsyncPageable <string> outgoingRelationships = client.GetRelationshipsAsync(floorTwinId);

                int outgoingRelationshipPageCount = 0;
                await foreach (Page <string> outgoingRelationshipPage in outgoingRelationships.AsPages())
                {
                    outgoingRelationshipPageCount++;
                    if (outgoingRelationshipPage.ContinuationToken != null)
                    {
                        outgoingRelationshipPage.Values.Count.Should().Be(defaultRelationshipPageSize, "Unexpected page size for a non-terminal page");
                    }
                }

                outgoingRelationshipPageCount.Should().BeGreaterThan(1, "Expected more than one page of outgoing relationships");
            }
            finally
            {
                // clean up
                try
                {
                    await Task
                    .WhenAll(
                        client.DeleteDigitalTwinAsync(floorTwinId),
                        client.DeleteDigitalTwinAsync(roomTwinId),
                        client.DeleteDigitalTwinAsync(hvacTwinId),
                        client.DeleteModelAsync(hvacModelId),
                        client.DeleteModelAsync(floorModelId),
                        client.DeleteModelAsync(roomModelId))
                    .ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    Assert.Fail($"Test clean up failed: {ex.Message}");
                }
            }
        }