Manage a graph of digital twins using relationships

The heart of Azure Digital Twins is the twin graph representing your whole environment. The twin graph is made of individual digital twins connected via relationships. This article focuses on managing relationships and the graph as a whole; to work with individual digital twins, see Manage digital twins.

Once you have a working Azure Digital Twins instance and have set up authentication code in your client app, you can create, modify, and delete digital twins and their relationships in an Azure Digital Twins instance.

Prerequisites

To work with Azure Digital Twins in this article, you'll need an Azure Digital Twins instance and the required permissions for using it. If you already have an Azure Digital Twins instance set up, you can use that instance and skip to the next section. Otherwise, follow the instructions in Set up an instance and authentication. The instructions contain information to help you verify that you've completed each step successfully.

After you set up your instance, make a note of the instance's host name. You can find the host name in the Azure portal.

Developer interfaces

This article highlights how to complete different management operations using the .NET (C#) SDK. You can also craft these same management calls using the other language SDKs described in Azure Digital Twins APIs and SDKs.

Other developer interfaces that can be used to complete these operations include:

Visualization

Azure Digital Twins Explorer is a visual tool for exploring the data in your Azure Digital Twins graph. You can use the explorer to view, query, and edit your models, twins, and relationships.

To read about the Azure Digital Twins Explorer tool, see Azure Digital Twins Explorer. For detailed steps on how to use its features, see Use Azure Digital Twins Explorer.

Here's what the visualization looks like:

Screenshot of Azure Digital Twins Explorer showing sample models and twins.

Create relationships

Relationships describe how different digital twins are connected to each other, which forms the basis of the twin graph.

The types of relationships that can be created from one (source) twin to another (target) twin are defined as part of the source twin's DTDL model. You can create an instance of a relationship by using the CreateOrReplaceRelationshipAsync() SDK call with twins and relationship details that follow the DTDL definition.

To create a relationship, you need to specify:

  • The source twin ID (srcId in the code sample below): The ID of the twin where the relationship originates.
  • The target twin ID (targetId in the code sample below): The ID of the twin where the relationship arrives.
  • A relationship name (relName in the code sample below): The generic type of relationship, something like contains.
  • A relationship ID (relId in the code sample below): The specific name for this relationship, something like Relationship1.

The relationship ID must be unique within the given source twin. It doesn't need to be globally unique. For example, for the twin Foo, each specific relationship ID must be unique. However, another twin Bar can have an outgoing relationship that matches the same ID of a Foo relationship.

The following code sample illustrates how to create a relationship in your Azure Digital Twins instance. It uses the SDK call (highlighted) inside a custom method that might appear in the context of a larger program.

private async static Task CustomMethod_CreateRelationshipAsync(DigitalTwinsClient client, string srcId, string targetId, string relName, IDictionary<string,object> inputProperties)
{
    var relationship = new BasicRelationship
    {
        TargetId = targetId,
        Name = relName,
        Properties = inputProperties
    };

    try
    {
        string relId = $"{srcId}-{relName}->{targetId}";
        await client.CreateOrReplaceRelationshipAsync<BasicRelationship>(srcId, relId, relationship);
        Console.WriteLine($"Created {relName} relationship successfully. Relationship ID is {relId}.");
    }
    catch (RequestFailedException rex)
    {
        Console.WriteLine($"Create relationship error: {rex.Status}:{rex.Message}");
    }

}

This custom function can now be called to create a contains relationship in the following way:

await CustomMethod_CreateRelationshipAsync(client, srcId, targetId, "contains", properties);

If you wish to create multiple relationships, you can repeat calls to the same method, passing different relationship types into the argument.

For more information on the helper class BasicRelationship, see Azure Digital Twins APIs and SDKs.

Create multiple relationships between twins

Relationships can be classified as either:

  • Outgoing relationships: Relationships belonging to this twin that point outward to connect it to other twins. The GetRelationshipsAsync() method is used to get outgoing relationships of a twin.
  • Incoming relationships: Relationships belonging to other twins that point towards this twin to create an "incoming" link. The GetIncomingRelationshipsAsync() method is used to get incoming relationships of a twin.

There's no restriction on the number of relationships that you can have between two twins—you can have as many relationships between twins as you like.

This fact means that you can express several different types of relationships between two twins at once. For example, Twin A can have both a stored relationship and manufactured relationship with Twin B.

You can even create multiple instances of the same type of relationship between the same two twins, if you want. In this example, Twin A could have two different stored relationships with Twin B, as long as the relationships have different relationship IDs.

Note

The DTDL attributes of minMultiplicity and maxMultiplicity for relationships aren't currently supported in Azure Digital Twins—even if they're defined as part of a model, they won't be enforced by the service. For more information, see Service-specific DTDL notes.

Create relationships in bulk with the Import Jobs API

You can use the Import Jobs API to create many relationships at once in a single API call. This method requires the use of Azure Blob Storage, as well as write permissions in your Azure Digital Twins instance for relationships and bulk jobs.

Tip

The Import Jobs API also allows models and twins to be imported in the same call, to create all parts of a graph at once. For more about this process, see Upload models, twins, and relationships in bulk with the Import Jobs API.

To import relationships in bulk, you'll need to structure your relationships (and any other resources included in the bulk import job) as an NDJSON file. The Relationships section comes after the Twins section, making it the last graph data section in the file. Relationships defined in the file can reference twins that are either defined in this file or already present in the instance, and they can optionally include initialization of any properties that the relationships have.

You can view an example import file and a sample project for creating these files in the Import Jobs API introduction.

Next, the file needs to be uploaded into an append blob in Azure Blob Storage. For instructions on how to create an Azure storage container, see Create a container. Then, upload the file using your preferred upload method (some options are the AzCopy command, the Azure CLI, or the Azure portal).

Once the NDJSON file has been uploaded to the container, get its URL within the blob container. You'll use this value later in the body of the bulk import API call.

Here's a screenshot showing the URL value of a blob file in the Azure portal:

Screenshot of the Azure portal showing the URL of a file in a storage container.

Then, the file can be used in an Import Jobs API call. You'll provide the blob storage URL of the input file, as well as a new blob storage URL to indicate where you'd like the output log to be stored when it's created by the service.

List relationships

List properties of a single relationship

You can always deserialize relationship data to a type of your choice. For basic access to a relationship, use the type BasicRelationship. The BasicRelationship helper class also gives you access to properties defined on the relationship, through an IDictionary<string, object>. To list properties, you can use:

public async Task ListRelationshipProperties(DigitalTwinsClient client, string twinId, string relId, BasicDigitalTwin twin)
{

    var res = await client.GetRelationshipAsync<BasicRelationship>(twinId, relId);
    BasicRelationship rel = res.Value;
    Console.WriteLine($"Relationship Name: {rel.Name}");
    foreach (string prop in rel.Properties.Keys)
    {
        if (twin.Contents.TryGetValue(prop, out object value))
        {
            Console.WriteLine($"Property '{prop}': {value}");
        }
    }
}

List outgoing relationships from a digital twin

To access the list of outgoing relationships for a given twin in the graph, you can use the GetRelationships() method like this:

AsyncPageable<BasicRelationship> rels = client.GetRelationshipsAsync<BasicRelationship>(dtId);

This method returns an Azure.Pageable<T> or Azure.AsyncPageable<T>, depending on whether you use the synchronous or asynchronous version of the call.

Here's an example that retrieves a list of relationships. It uses the SDK call (highlighted) inside a custom method that might appear in the context of a larger program.

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
        AsyncPageable<BasicRelationship> rels = client.GetRelationshipsAsync<BasicRelationship>(dtId);
        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;
    }
}

You can now call this custom method to see the outgoing relationships of the twins like this:

await CustomMethod_FindOutgoingRelationshipsAsync(client, twin_Id);

You can use the retrieved relationships to navigate to other twins in your graph by reading the target field from the relationship that is returned, and using it as the ID for your next call to GetDigitalTwin().

List incoming relationships to a digital twin

Azure Digital Twins also has an SDK call to find all incoming relationships to a given twin. This SDK is often useful for reverse navigation, or when deleting a twin.

Note

IncomingRelationship calls don't return the full body of the relationship. For more information on the IncomingRelationship class, see its reference documentation.

The code sample in the previous section focused on finding outgoing relationships from a twin. The following example is structured similarly, but finds incoming relationships to the twin instead. This example also uses the SDK call (highlighted) inside a custom method that might appear in the context of a larger program.

private static async Task<List<IncomingRelationship>> CustomMethod_FindIncomingRelationshipsAsync(DigitalTwinsClient client, string dtId)
{
    // Find the relationships for the twin
    
    try
    {
        // GetRelationshipsAsync will throw an error if a problem occurs
        AsyncPageable<IncomingRelationship> incomingRels = client.GetIncomingRelationshipsAsync(dtId);

        var results = new List<IncomingRelationship>();
        await foreach (IncomingRelationship incomingRel in incomingRels)
        {
            results.Add(incomingRel);
            Console.WriteLine($"Found incoming relationship: {incomingRel.RelationshipId}");

            //Print its properties
            Response<BasicRelationship> relResponse = await client.GetRelationshipAsync<BasicRelationship>(incomingRel.SourceId, incomingRel.RelationshipId);
            BasicRelationship rel = relResponse.Value;
            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 incoming relationships for {dtId} due to {ex.Message}");
        return null;
    }
}

You can now call this custom method to see the incoming relationships of the twins like this:

await CustomMethod_FindIncomingRelationshipsAsync(client, twin_Id);

List all twin properties and relationships

Using the above methods for listing outgoing and incoming relationships to a twin, you can create a method that prints full twin information, including the twin's properties and both types of its relationships. Here's an example custom method showing how to combine the above custom methods for this purpose.

private static async Task CustomMethod_FetchAndPrintTwinAsync(string twin_Id, DigitalTwinsClient client)
{
    Response<BasicDigitalTwin> res = await client.GetDigitalTwinAsync<BasicDigitalTwin>(twin_Id);
    await CustomMethod_FindOutgoingRelationshipsAsync(client, twin_Id);
    await CustomMethod_FindIncomingRelationshipsAsync(client, twin_Id);

    return;
}

You can now call this custom function like this:

await CustomMethod_FetchAndPrintTwinAsync(srcId, client);

Update relationships

Relationships are updated using the UpdateRelationship method.

Note

This method is for updating the properties of a relationship. If you need to change the source twin or target twin of the relationship, you'll need to delete the relationship and re-create one using the new twins.

The required parameters for the client call are:

  • The ID of the source twin (the twin where the relationship originates).
  • The ID of the relationship to update.
  • A JSON Patch document containing the properties and new values you want to update.

Here's a sample code snippet showing how to use this method. This example uses the SDK call (highlighted) inside a custom method that might appear in the context of a larger program.

private async static Task CustomMethod_UpdateRelationshipAsync(DigitalTwinsClient client, string srcId, string relId, Azure.JsonPatchDocument updateDocument)
{

    try
    {
        await client.UpdateRelationshipAsync(srcId, relId, updateDocument);
        Console.WriteLine($"Successfully updated {relId}");
    }
    catch (RequestFailedException rex)
    {
        Console.WriteLine($"Update relationship error: {rex.Status}:{rex.Message}");
    }

}

Here's an example of a call to this custom method, passing in a JSON Patch document with the information to update a property.

var updatePropertyPatch = new JsonPatchDocument();
updatePropertyPatch.AppendAdd("/ownershipUser", "ownershipUser NEW value");
await CustomMethod_UpdateRelationshipAsync(client, srcId, $"{srcId}-contains->{targetId}", updatePropertyPatch);

Delete relationships

The first parameter specifies the source twin (the twin where the relationship originates). The other parameter is the relationship ID. You need both the twin ID and the relationship ID, because relationship IDs are only unique within the scope of a twin.

Here's sample code showing how to use this method. This example uses the SDK call (highlighted) inside a custom method that might appear in the context of a larger program.

private static async Task CustomMethod_DeleteRelationshipAsync(DigitalTwinsClient client, string srcId, string relId)
{
    try
    {
        Response response = await client.DeleteRelationshipAsync(srcId, relId);
        await CustomMethod_FetchAndPrintTwinAsync(srcId, client);
        Console.WriteLine("Deleted relationship successfully");
    }
    catch (RequestFailedException e)
    {
        Console.WriteLine($"Error {e.ErrorCode}");
    }
}

You can now call this custom method to delete a relationship like this:

await CustomMethod_DeleteRelationshipAsync(client, srcId, $"{srcId}-contains->{targetId}");

Note

If you want to delete all models, twins, and relationships in an instance at once, use the Delete Jobs API.

Create multiple graph elements at once

This section describes strategies for creating a graph with multiple elements at the same time, rather than using individual API calls to upload models, twins, and relationships to upload them one by one.

Upload models, twins, and relationships in bulk with the Import Jobs API

You can use the Import Jobs API to upload multiple models, twins, and relationships to your instance in a single API call, effectively creating the graph all at once. This method requires the use of Azure Blob Storage, as well as write permissions in your Azure Digital Twins instance for graph elements (models, twins, and relationships) and bulk jobs.

To import resources in bulk, start by creating an NDJSON file containing the details of your resources. The file starts with a Header section, followed by the optional sections Models, Twins, and Relationships. You don't have to include all three types of graph data in the file, but any sections that are present must follow that order. Twins defined in the file can reference models that are either defined in this file or already present in the instance, and they can optionally include initialization of the twin's properties. Relationships defined in the file can reference twins that are either defined in this file or already present in the instance, and they can optionally include initialization of relationship properties.

You can view an example import file and a sample project for creating these files in the Import Jobs API introduction.

Next, the file needs to be uploaded into an append blob in Azure Blob Storage. For instructions on how to create an Azure storage container, see Create a container. Then, upload the file using your preferred upload method (some options are the AzCopy command, the Azure CLI, or the Azure portal).

Once the NDJSON file has been uploaded to the container, get its URL within the blob container. You'll use this value later in the body of the bulk import API call.

Here's a screenshot showing the URL value of a blob file in the Azure portal:

Screenshot of the Azure portal showing the URL of a file in a storage container.

Then, the file can be used in an Import Jobs API call. You'll provide the blob storage URL of the input file, as well as a new blob storage URL to indicate where you'd like the output log to be stored when it's created by the service.

Import graph with Azure Digital Twins Explorer

Azure Digital Twins Explorer is a visual tool for viewing and interacting with your twin graph. It contains a feature for importing a graph file in either JSON or Excel format that can contain multiple models, twins, and relationships.

For detailed information about using this feature, see Import graph in the Azure Digital Twins Explorer documentation.

Create twins and relationships from a CSV file

Sometimes, you might need to create twin hierarchies out of data stored in a different database, or in a spreadsheet or a CSV file. This section illustrates how to read data from a CSV file and create a twin graph out of it.

Consider the following data table, describing a set of digital twins and relationships. The models referenced in this file must already exist in the Azure Digital Twins instance.

Model ID Twin ID (must be unique) Relationship name Target twin ID Twin init data
dtmi:example:Floor;1 Floor1 contains Room1
dtmi:example:Floor;1 Floor0 contains Room0
dtmi:example:Room;1 Room1 {"Temperature": 80}
dtmi:example:Room;1 Room0 {"Temperature": 70}

One way to get this data into Azure Digital Twins is to convert the table to a CSV file. Once the table is converted, code can be written to interpret the file into commands to create twins and relationships. The following code sample illustrates reading the data from the CSV file and creating a twin graph in Azure Digital Twins.

In the code below, the CSV file is called data.csv, and there's a placeholder representing the host name of your Azure Digital Twins instance. The sample also makes use of several packages that you can add to your project to help with this process.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure;
using Azure.DigitalTwins.Core;
using Azure.Identity;

namespace creating_twin_graph_from_csv
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var relationshipRecordList = new List<BasicRelationship>();
            var twinList = new List<BasicDigitalTwin>();
            List<List<string>> data = ReadData();
            DigitalTwinsClient client = CreateDtClient();

            // Interpret the CSV file data, by each row
            foreach (List<string> row in data)
            {
                string modelID = row.Count > 0 ? row[0].Trim() : null;
                string srcID = row.Count > 1 ? row[1].Trim() : null;
                string relName = row.Count > 2 ? row[2].Trim() : null;
                string targetID = row.Count > 3 ? row[3].Trim() : null;
                string initProperties = row.Count > 4 ? row[4].Trim() : null;
                Console.WriteLine($"ModelID: {modelID}, TwinID: {srcID}, RelName: {relName}, TargetID: {targetID}, InitData: {initProperties}");
                var props = new Dictionary<string, object>();
                // Parse properties into dictionary (left out for compactness)
                // ...

                // Null check for source and target IDs
                if (!string.IsNullOrWhiteSpace(srcID) && !string.IsNullOrWhiteSpace(targetID) && !string.IsNullOrWhiteSpace(relName))
                {
                    relationshipRecordList.Add(
                        new BasicRelationship
                        {
                            SourceId = srcID,
                            TargetId = targetID,
                            Name = relName,
                        });
                }

                if (!string.IsNullOrWhiteSpace(srcID) && !string.IsNullOrWhiteSpace(modelID))
                twinList.Add(
                    new BasicDigitalTwin
                    {
                        Id = srcID,
                        Metadata = { ModelId = modelID },
                        Contents = props,
                    });
            }

            // Create digital twins
            foreach (BasicDigitalTwin twin in twinList)
            {
                try
                {
                    await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(twin.Id, twin);
                    Console.WriteLine("Twin is created");
                }
                catch (RequestFailedException ex)
                {
                    Console.WriteLine($"Error {ex.Status}: {ex.Message}");
                }
            }

            // Create relationships between the twins
            foreach (BasicRelationship rec in relationshipRecordList)
            {
                string relId = $"{rec.SourceId}-{rec.Name}->{rec.TargetId}";
                try
                {
                    await client.CreateOrReplaceRelationshipAsync<BasicRelationship>(rec.SourceId, relId, rec);
                    Console.WriteLine($"Relationship {relId} is created");
                }
                catch (RequestFailedException ex)
                {
                    Console.WriteLine($"Error creating relationship {relId}. {ex.Status}: {ex.Message}");
                }
            }
        }

        // Method to ingest data from the CSV file
        public static List<List<string>> ReadData()
        {
            string path = "<path-to>/data.csv";
            string[] lines = System.IO.File.ReadAllLines(path);
            var data = new List<List<string>>();
            int count = 0;
            foreach (string line in lines)
            {
                if (count++ == 0)
                    continue;
                var cols = new List<string>();
                string[] columns = line.Split(',');
                foreach (string column in columns)
                {
                    cols.Add(column);
                }
                data.Add(cols);
            }
            return data;
        }

        // Method to create the digital twins client
        private static DigitalTwinsClient CreateDtClient()
        {
            string adtInstanceUrl = "https://<your-instance-hostname>";
            var credentials = new DefaultAzureCredential();
            return new DigitalTwinsClient(new Uri(adtInstanceUrl), credentials);
        }
    }
}

Runnable twin graph sample

The following runnable code snippet uses the relationship operations from this article to create a twin graph out of digital twins and relationships.

Set up sample project files

The snippet uses two sample model definitions, Room.json and Floor.json. To download the model files so you can use them in your code, use these links to go directly to the files in GitHub. Then, right-click anywhere on the screen, select Save as in your browser's right-click menu, and use the Save As window to save the files as Room.json and Floor.json.

Next, create a new console app project in Visual Studio or your editor of choice.

Then, copy the following code of the runnable sample into your project:

using System;
using System.Threading.Tasks;
using System.IO;
using System.Collections.Generic;
using Azure;
using Azure.DigitalTwins.Core;
using Azure.Identity;

namespace DigitalTwins_Samples
{
    public class GraphOperationsSample
    {
        public static async Task Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            // Create the Azure Digital Twins client for API calls
            DigitalTwinsClient client = createDtClient();
            Console.WriteLine($"Service client created – ready to go");
            Console.WriteLine();

            // Upload models
            Console.WriteLine($"Upload models");
            Console.WriteLine();
            string dtdl = File.ReadAllText("<path-to>/Room.json");
            string dtdl1 = File.ReadAllText("<path-to>/Floor.json");
            var models = new List<string>
            {
                dtdl,
                dtdl1,
            };
            // Upload the models to the service
            await client.CreateModelsAsync(models);

            // Create new (Floor) digital twin
            var floorTwin = new BasicDigitalTwin();
            string srcId = "myFloorID";
            floorTwin.Metadata.ModelId = "dtmi:example:Floor;1";
            // Floor twins have no properties, so nothing to initialize
            // Create the twin
            await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(srcId, floorTwin);
            Console.WriteLine("Twin created successfully");

            // Create second (Room) digital twin
            var roomTwin = new BasicDigitalTwin();
            string targetId = "myRoomID";
            roomTwin.Metadata.ModelId = "dtmi:example:Room;1";
            // Initialize properties
            roomTwin.Contents.Add("Temperature", 35.0);
            roomTwin.Contents.Add("Humidity", 55.0);
            // Create the twin
            await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(targetId, roomTwin);
            
            // Create relationship between them
            var properties = new Dictionary<string, object>
            {
                { "ownershipUser", "ownershipUser original value" },
            };
            // <UseCreateRelationship>
            await CustomMethod_CreateRelationshipAsync(client, srcId, targetId, "contains", properties);
            // </UseCreateRelationship>
            Console.WriteLine();

            // Update relationship's Name property
            // <UseUpdateRelationship>
            var updatePropertyPatch = new JsonPatchDocument();
            updatePropertyPatch.AppendAdd("/ownershipUser", "ownershipUser NEW value");
            await CustomMethod_UpdateRelationshipAsync(client, srcId, $"{srcId}-contains->{targetId}", updatePropertyPatch);
            // </UseUpdateRelationship>
            Console.WriteLine();

            //Print twins and their relationships
            Console.WriteLine("--- Printing details:");
            Console.WriteLine($"Outgoing relationships from source twin, {srcId}:");
            // <UseFetchAndPrint>
            await CustomMethod_FetchAndPrintTwinAsync(srcId, client);
            // </UseFetchAndPrint>
            Console.WriteLine();
            Console.WriteLine($"Incoming relationships to target twin, {targetId}:");
            await CustomMethod_FetchAndPrintTwinAsync(targetId, client);
            Console.WriteLine("--------");
            Console.WriteLine();

            // Delete the relationship
            // <UseDeleteRelationship>
            await CustomMethod_DeleteRelationshipAsync(client, srcId, $"{srcId}-contains->{targetId}");
            // </UseDeleteRelationship>
            Console.WriteLine();

            // Print twins and their relationships again
            Console.WriteLine("--- Printing details (after relationship deletion):");
            Console.WriteLine("Outgoing relationships from source twin:");
            await CustomMethod_FetchAndPrintTwinAsync(srcId, client);
            Console.WriteLine();
            Console.WriteLine("Incoming relationships to target twin:");
            await CustomMethod_FetchAndPrintTwinAsync(targetId, client);
            Console.WriteLine("--------");
            Console.WriteLine();
        }

        private static DigitalTwinsClient createDtClient()
        {
            string adtInstanceUrl = "https://<your-instance-hostname>";
            var credentials = new DefaultAzureCredential();
            var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), credentials);
            return client;
        }

        // <CreateRelationshipMethod>
        private async static Task CustomMethod_CreateRelationshipAsync(DigitalTwinsClient client, string srcId, string targetId, string relName, IDictionary<string,object> inputProperties)
        {
            var relationship = new BasicRelationship
            {
                TargetId = targetId,
                Name = relName,
                Properties = inputProperties
            };

            try
            {
                string relId = $"{srcId}-{relName}->{targetId}";
                await client.CreateOrReplaceRelationshipAsync<BasicRelationship>(srcId, relId, relationship);
                Console.WriteLine($"Created {relName} relationship successfully. Relationship ID is {relId}.");
            }
            catch (RequestFailedException rex)
            {
                Console.WriteLine($"Create relationship error: {rex.Status}:{rex.Message}");
            }

        }
        // </CreateRelationshipMethod>

        // <UpdateRelationshipMethod>
        private async static Task CustomMethod_UpdateRelationshipAsync(DigitalTwinsClient client, string srcId, string relId, Azure.JsonPatchDocument updateDocument)
        {

            try
            {
                await client.UpdateRelationshipAsync(srcId, relId, updateDocument);
                Console.WriteLine($"Successfully updated {relId}");
            }
            catch (RequestFailedException rex)
            {
                Console.WriteLine($"Update relationship error: {rex.Status}:{rex.Message}");
            }

        }
        // </UpdateRelationshipMethod>

        // <FetchAndPrintMethod>
        private static async Task CustomMethod_FetchAndPrintTwinAsync(string twin_Id, DigitalTwinsClient client)
        {
            Response<BasicDigitalTwin> res = await client.GetDigitalTwinAsync<BasicDigitalTwin>(twin_Id);
            // <UseFindOutgoingRelationships>
            await CustomMethod_FindOutgoingRelationshipsAsync(client, twin_Id);
            // </UseFindOutgoingRelationships>
            // <UseFindIncomingRelationships>
            await CustomMethod_FindIncomingRelationshipsAsync(client, twin_Id);
            // </UseFindIncomingRelationships>

            return;
        }
        // </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;
            }
        }
        // </FindOutgoingRelationshipsMethod>

        // <FindIncomingRelationshipsMethod>
        private static async Task<List<IncomingRelationship>> CustomMethod_FindIncomingRelationshipsAsync(DigitalTwinsClient client, string dtId)
        {
            // Find the relationships for the twin
            
            try
            {
                // GetRelationshipsAsync will throw an error if a problem occurs
                AsyncPageable<IncomingRelationship> incomingRels = client.GetIncomingRelationshipsAsync(dtId);

                var results = new List<IncomingRelationship>();
                await foreach (IncomingRelationship incomingRel in incomingRels)
                {
                    results.Add(incomingRel);
                    Console.WriteLine($"Found incoming relationship: {incomingRel.RelationshipId}");

                    //Print its properties
                    Response<BasicRelationship> relResponse = await client.GetRelationshipAsync<BasicRelationship>(incomingRel.SourceId, incomingRel.RelationshipId);
                    BasicRelationship rel = relResponse.Value;
                    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 incoming relationships for {dtId} due to {ex.Message}");
                return null;
            }
        }
        // </FindIncomingRelationshipsMethod>

        // <DeleteRelationshipMethod>
        private static async Task CustomMethod_DeleteRelationshipAsync(DigitalTwinsClient client, string srcId, string relId)
        {
            try
            {
                Response response = await client.DeleteRelationshipAsync(srcId, relId);
                await CustomMethod_FetchAndPrintTwinAsync(srcId, client);
                Console.WriteLine("Deleted relationship successfully");
            }
            catch (RequestFailedException e)
            {
                Console.WriteLine($"Error {e.ErrorCode}");
            }
        }
        // </DeleteRelationshipMethod>
    }
}

Note

There's currently a known issue affecting the DefaultAzureCredential wrapper class that may result in an error while authenticating. If you encounter this issue, you can try instantiating DefaultAzureCredential with the following optional parameter to resolve it: new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true });

For more information about this issue, see Azure Digital Twins known issues.

Configure project

Next, complete the following steps to configure your project code:

  1. Add the Room.json and Floor.json files you downloaded earlier to your project, and replace the <path-to> placeholders in the code to tell your program where to find them.

  2. Replace the placeholder <your-instance-hostname> with your Azure Digital Twins instance's host name.

  3. Add two dependencies to your project that will be needed to work with Azure Digital Twins. The first is the package for the Azure Digital Twins SDK for .NET, and the second provides tools to help with authentication against Azure.

    dotnet add package Azure.DigitalTwins.Core
    dotnet add package Azure.Identity
    

You'll also need to set up local credentials if you want to run the sample directly. The next section walks through this process.

Set up local Azure credentials

This sample uses DefaultAzureCredential (part of the Azure.Identity library) to authenticate users with the Azure Digital Twins instance when you run it on your local machine. For more information on different ways a client app can authenticate with Azure Digital Twins, see Write app authentication code.

With DefaultAzureCredential, the sample will search for credentials in your local environment, like an Azure sign-in in a local Azure CLI or in Visual Studio or Visual Studio Code. For this reason, you should sign in to Azure locally through one of these mechanisms to set up credentials for the sample.

If you're using Visual Studio or Visual Studio Code to run code samples, make sure you're signed in to that editor with the same Azure credentials that you want to use to access your Azure Digital Twins instance. If you're using a local CLI window, run the az login command to sign in to your Azure account. After this, when you run your code sample, you should be authenticated automatically.

Run the sample

Now that you've completed setup, you can run the sample code project.

Here's the console output of the program:

Screenshot of the console output showing the twin details with incoming and outgoing relationships of the twins.

Tip

The twin graph is a concept of creating relationships between twins. If you want to view the visual representation of the twin graph, see the Visualization section of this article.

Next steps

Learn about querying an Azure Digital Twins twin graph: