Administración de un grafo de gemelos digitales con relaciones

El corazón de Azure Digital Twins es el grafo de gemelos que representa todo el entorno. El grafo de gemelos se compone de gemelos digitales individuales conectados mediante relaciones. Este artículo está centrado en la administración de las relaciones y el grafo en conjunto. Si desea trabajar con gemelos digitales individuales, consulte Administración de Digital Twins.

Una vez que tenga una instancia de Azure Digital Twins en funcionamiento y que haya configurado el código de autenticación en la aplicación cliente, puede crear, modificar y eliminar gemelos digitales y sus relaciones en una instancia de Azure Digital Twins.

Requisitos previos

Para poder seguir los pasos que se describen en este artículo y trabajar con Azure Digital Twins, es preciso configurar una instancia de Azure Digital Twins y los permisos necesarios para usarla. Si ya tiene una instancia configurada de Azure Digital Twins, puede usarla y pasar a la sección siguiente. De lo contrario, siga las instrucciones que se indican en Configuración de una instancia y autenticación. Las instrucciones contienen información que le ayudará a comprobar que ha completado cada paso correctamente.

Una vez configurada la instancia, anote el nombre de host de esta. El nombre de host se encuentra en Azure Portal.

Interfaces para desarrolladores

En este artículo se describe cómo completar diferentes operaciones de administración con el SDK de .NET (C#). También puede crear estas mismas llamadas de administración con los otros SDK de lenguaje descritos en API y SDK de Azure Digital Twins.

Otras interfaces de desarrollador que se pueden usar para completar estas operaciones incluyen:

Visualización

Azure Digital Twins Explorer es una herramienta visual para explorar los datos en el grafo de Azure Digital Twins. Puede usar el explorador para ver, consultar y editar los modelos, los gemelos y las relaciones.

Para obtener información sobre la herramienta Azure Digital Twins Explorer, vea Azure Digital Twins Explorer. Para obtener pasos detallados sobre el uso de sus características, consulte Uso de Azure Digital Twins Explorer.

Este es el aspecto de la visualización:

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

Crear relaciones

Las relaciones describen cómo los diferentes gemelos digitales se conectan entre sí, lo que constituye la base del grafo de gemelos.

Los tipos de relaciones que se pueden crear de un gemelo (origen) a otro (destino) se definen como parte del modelo DTDL del gemelo de origen. Puede crear una instancia de una relación mediante la llamada al SDK CreateOrReplaceRelationshipAsync() con detalles sobre los gemelos y la relación que cumplan con la definición DTDL.

Para crear una relación, debe especificar:

  • El identificador del gemelo de origen (srcId en el ejemplo de código siguiente): el identificador del gemelo donde se origina la relación.
  • Identificador del gemelo de destino (targetId en el ejemplo de código siguiente): el identificador del gemelo donde llega la relación.
  • Un nombre de relación (relName en el siguiente ejemplo de código): tipo genérico de relación, algo como contiene.
  • Un Id. de relación (relId en el siguiente ejemplo de código): nombre específico de esta relación, algo como Relación1.

El id. de relación debe ser único dentro del gemelo de origen especificado. No es necesario que sea único globalmente. Por ejemplo, para el gemelo Foo, cada identificador de relación específico debe ser único. Sin embargo, otro gemelo llamado Bar puede tener una relación de salida que coincida con el mismo identificador de una relación de Foo.

En el ejemplo de código siguiente se muestra cómo crear una relación en la instancia de Azure Digital Twins. Usa la llamada de SDK (resaltada) de un método personalizado que puede aparecer en el contexto de un programa más grande.

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}");
    }

}

Ahora se puede llamar a esta función personalizada para crear una relación contiene de esta manera:

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

Si desea crear varias relaciones, puede repetir las llamadas al mismo método, con tipos de relación diferentes en el argumento.

Para más información sobre la clase auxiliar BasicRelationship, consulte API y SDK de Azure Digital Twins.

Creación de varias relaciones entre gemelos

Las relaciones se pueden clasificar como:

  • Relaciones de salida: relaciones que pertenecen a este gemelo que apuntan hacia afuera para conectarlo con otros gemelos. El método GetRelationshipsAsync() se usa para obtener las relaciones de salida de un gemelo.
  • Relaciones de entrada: relaciones que pertenecen a otros gemelos que apuntan hacia este gemelo para crear un vínculo "entrante". El método GetIncomingRelationshipsAsync() se usa para obtener las relaciones de entrada de un gemelo.

No hay ninguna restricción en el número de relaciones que puede tener entre dos gemelos: puede tener tantas como desee.

Este hecho significa que puede expresar varios tipos diferentes de relaciones entre dos gemelos a la vez. Por ejemplo, el gemelo A puede tener una relación almacenada y una relación creada con el gemelo B.

También puede crear varias instancias del mismo tipo de relación entre los mismos dos gemelos, si lo desea. En este ejemplo, el gemelo A podría tener dos relaciones almacenadas diferentes con el gemelo B, siempre y cuando las relaciones tengan identificadores de relación diferentes.

Nota:

Los atributos DTDL de minMultiplicity y maxMultiplicity para las relaciones no se admiten actualmente en Azure Digital Twins: aunque se definan como parte de un modelo, el servicio no los aplicará. Para más información, consulte Notas de DTDL específicas del servicio.

Creación de relaciones de forma masiva con la API de importación de trabajos

Puede usar import Jobs API para crear muchas relaciones a la vez en una sola llamada API. Este método requiere el uso de Azure Blob Storage, así como permisos de escritura en la instancia de Azure Digital Twins para relaciones y trabajos masivos.

Sugerencia

La API De trabajos de importación también permite importar modelos y gemelos en la misma llamada, para crear todas las partes de un grafo a la vez. Para obtener más información sobre este proceso, consulte Carga de modelos, gemelos y relaciones de forma masiva con la API de importación de trabajos.

Para importar relaciones de forma masiva, deberá estructurar las relaciones (y cualquier otro recurso incluido en el trabajo de importación masiva) como un archivo NDJSON . La Relationships sección viene después de la Twins sección, convirtiéndola en la última sección de datos del grafo del archivo. Las relaciones definidas en el archivo pueden hacer referencia a gemelos que se definen en este archivo o que ya están presentes en la instancia, y pueden incluir opcionalmente la inicialización de cualquier propiedad que tengan las relaciones.

Puede ver un archivo de importación de ejemplo y un proyecto de ejemplo para crear estos archivos en la introducción a la API de trabajos de importación.

A continuación, el archivo debe cargarse en un blob en anexos en Azure Blob Storage. Para obtener instrucciones sobre cómo crear un contenedor de Azure Storage, consulte Creación de un contenedor. A continuación, cargue el archivo mediante el método de carga preferido (algunas opciones son el comando AzCopy, la CLI de Azure o Azure Portal).

Una vez cargado el archivo NDJSON en el contenedor, obtenga su dirección URL dentro del contenedor de blobs. Usará este valor más adelante en el cuerpo de la llamada API de importación masiva.

Esta es una captura de pantalla que muestra el valor de dirección URL de un archivo de blob en Azure Portal:

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

A continuación, el archivo se puede usar en una llamada API de trabajos de importación. Proporcionará la dirección URL del almacenamiento de blobs del archivo de entrada, así como una nueva dirección URL de almacenamiento de blobs para indicar dónde desea que el registro de salida se almacene cuando el servicio lo cree.

Enumeración de las relaciones

Enumeración de las propiedades de una relación única

Siempre puede deserializar los datos de la relación en un tipo de su elección. Para obtener acceso básico a una relación, utilice el tipo BasicRelationship. La clase de asistente BasicRelationship también proporciona acceso a las propiedades definidas en la relación, mediante un elemento IDictionary<string, object>. Para enumerar las propiedades, puede usar:

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}");
        }
    }
}

Enumeración de las relaciones de salida desde un gemelo digital

Para acceder a la lista de relaciones de salida de un gemelo determinado del grafo, puede usar el método GetRelationships() de la siguiente forma:

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

Este método devuelve un elemento Azure.Pageable<T> o Azure.AsyncPageable<T>, en función de si se usa la versión sincrónica o asincrónica de la llamada.

Este es un ejemplo que recupera una lista de relaciones. Usa la llamada de SDK (resaltada) de un método personalizado que puede aparecer en el contexto de un programa más grande.

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;
    }
}

Ahora puede llamar a este método personalizado para ver las relaciones de salida de los gemelos como se muestra a continuación:

await CustomMethod_FindOutgoingRelationshipsAsync(client, twin_Id);

Puede usar las relaciones recuperadas para navegar a otros gemelos en el grafo. Para ello, lea el campo target de la relación que se devuelve y úselo como el identificador para la llamada siguiente a GetDigitalTwin().

Enumeración de relaciones de entrada para un gemelo digital

Azure Digital Twins también dispone de una llamada de SDK para buscar todas las relaciones de entrada para un gemelo determinado. Este SDK suele ser útil para la navegación inversa o cuando se elimina un gemelo.

Nota:

Las llamadas a IncomingRelationship no devuelven todo el cuerpo de la relación. Para más información sobre la clase IncomingRelationship, consulte su documentación de referencia.

El ejemplo de código de la sección anterior se centraba en buscar relaciones de salida desde un gemelo. El ejemplo siguiente está estructurado de manera similar, pero en su lugar busca relaciones de entrada para el gemelo. En este ejemplo también se usa la llamada de SDK (resaltada) de un método personalizado que puede aparecer en el contexto de un programa más grande.

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;
    }
}

Ahora puede llamar a este método personalizado para ver las relaciones de entrada de los gemelos como se muestra a continuación:

await CustomMethod_FindIncomingRelationshipsAsync(client, twin_Id);

Enumeración de todas las propiedades y relaciones de un gemelo

Con los métodos anteriores para mostrar las relaciones de salida y de entrada de un gemelo, puede crear un método que imprima la información completa del gemelo, incluidas las propiedades del gemelo y ambos tipos de relaciones. Este es un método personalizado de ejemplo que muestra cómo combinar los métodos personalizados anteriores con este fin.

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;
}

Ahora puede llamar a esta función personalizada de la siguiente manera:

await CustomMethod_FetchAndPrintTwinAsync(srcId, client);

Actualizar relaciones

Las relaciones se actualizan mediante el método UpdateRelationship.

Nota:

Este método se usa para actualizar las propiedades de una relación. Si necesita cambiar el gemelo de origen o el de destino de la relación, deberá eliminar la relación y volver a crear una con los nuevos gemelos.

Los parámetros necesarios para la llamada de cliente son:

  • El identificador del gemelo de origen (el gemelo en el que se origina la relación).
  • El identificador de la relación que se va a actualizar.
  • Un documento de revisión JSON que contiene las propiedades y los valores nuevos que desea actualizar.

A continuación se muestra el fragmento de código de ejemplo que muestra cómo se usa este método. En este ejemplo se usa la llamada de SDK (resaltada) de un método personalizado que puede aparecer en el contexto de un programa más grande.

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}");
    }

}

A continuación se muestra un ejemplo de una llamada a este método personalizado que pasa un documento de revisión de JSON con la información para actualizar una propiedad.

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

Eliminar relaciones

El primer parámetro especifica el gemelo de origen (el gemelo en el que se origina la relación). El otro parámetro es el id. de relación. Necesita tanto el id. de gemelo como el id. de relación, ya que los id. de relación solo son únicos dentro del ámbito de un gemelo.

Este es el código de ejemplo que muestra cómo utilizar este método. En este ejemplo se usa la llamada de SDK (resaltada) de un método personalizado que puede aparecer en el contexto de un programa más grande.

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}");
    }
}

Ahora puede llamar a este método personalizado para eliminar una relación de la siguiente forma:

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

Nota:

Si desea eliminar todos los modelos, gemelos y relaciones de una instancia a la vez, use la API Eliminar trabajos.

Crear varios elementos de grafo a la vez

En esta sección se describen las estrategias para crear un grafo con varios elementos al mismo tiempo, en lugar de usar llamadas API individuales para cargar modelos, gemelos y relaciones para cargarlos uno por uno.

Carga de modelos, gemelos y relaciones de forma masiva con la API de importación de trabajos

Puede usar la API Importar trabajos para cargar varios modelos, gemelos y relaciones con la instancia en una sola llamada API, lo que crea de forma eficaz el grafo a la vez. Este método requiere el uso de Azure Blob Storage, así como permisos de escritura en la instancia de Azure Digital Twins para elementos de grafos (modelos, gemelos y relaciones) y trabajos masivos.

Para importar recursos de forma masiva, empiece por crear un archivo NDJSON que contenga los detalles de los recursos. El archivo comienza con una Header sección, seguida de las secciones Modelsopcionales , Twinsy Relationships. No es necesario incluir los tres tipos de datos de grafos en el archivo, pero las secciones presentes deben seguir ese orden. Los gemelos definidos en el archivo pueden hacer referencia a modelos que se definen en este archivo o que ya están presentes en la instancia y, opcionalmente, pueden incluir la inicialización de las propiedades del gemelo. Las relaciones definidas en el archivo pueden hacer referencia a gemelos que se definen en este archivo o que ya están presentes en la instancia y, opcionalmente, pueden incluir la inicialización de las propiedades de relación.

Puede ver un archivo de importación de ejemplo y un proyecto de ejemplo para crear estos archivos en la introducción a la API de trabajos de importación.

A continuación, el archivo debe cargarse en un blob en anexos en Azure Blob Storage. Para obtener instrucciones sobre cómo crear un contenedor de Azure Storage, consulte Creación de un contenedor. A continuación, cargue el archivo mediante el método de carga preferido (algunas opciones son el comando AzCopy, la CLI de Azure o Azure Portal).

Una vez cargado el archivo NDJSON en el contenedor, obtenga su dirección URL dentro del contenedor de blobs. Usará este valor más adelante en el cuerpo de la llamada API de importación masiva.

Esta es una captura de pantalla que muestra el valor de dirección URL de un archivo de blob en Azure Portal:

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

A continuación, el archivo se puede usar en una llamada API de trabajos de importación. Proporcionará la dirección URL del almacenamiento de blobs del archivo de entrada, así como una nueva dirección URL de almacenamiento de blobs para indicar dónde desea que el registro de salida se almacene cuando el servicio lo cree.

Importación de grafos con Azure Digital Twins Explorer

Azure Digital Twins Explorer es una herramienta visual para ver e interactuar con el grafo de gemelos. Contiene una característica para importar un archivo de grafo en formato JSON o Excel que puede contener varios modelos, gemelos y relaciones.

Para obtener información detallada sobre el uso de esta característica, consulte Importación de grafos en la documentación de Azure Digital Twins Explorer.

Creación de gemelos y relaciones a partir de un archivo CSV

A veces, es posible que tenga que crear jerarquías de gemelos fuera de los datos almacenados en una base de datos diferente o en una hoja de cálculo o en un archivo CSV. En esta sección se ilustra cómo leer datos de un archivo .csv y crear un grafo gemelo con ellos.

Observe la siguiente tabla de datos, que describe un conjunto de gemelos digitales y las relaciones. Los modelos a los que se hace referencia en este archivo ya deben existir en la instancia de Azure Digital Twins.

Id. de modelo Identificador del gemelo (debe ser único) Nombre de relación Identificador del gemelo de destino Datos de inicialización del gemelo
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}

Una forma de llevar estos datos s Azure Digital Twins es convertir la tabla en un archivo .csv. Una vez convertida la tabla, se puede escribir código para interpretar el archivo en comandos a fin de crear gemelos y relaciones. El siguiente código de ejemplo ilustra la lectura de los datos del archivo .csv y la creación de un grafo gemelo en Azure Digital Twins.

En el código siguiente, el archivo .csv se llama data.csv y hay un marcador de posición que representa el nombre de host de la instancia de Azure Digital Twins. El ejemplo también usa varios paquetes, que se pueden agregar al proyecto para que faciliten este proceso.

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);
        }
    }
}

Ejemplo de grafo de gemelos ejecutable

En el siguiente fragmento de código ejecutable se usan las operaciones de relación de este artículo para crear un grafo de gemelos a partir de gemelos digitales y relaciones.

Configuración de archivos del proyecto de ejemplo

El fragmento de código usa dos definiciones de modelo de ejemplo, Room.json y Floor.json. Para descargar los archivos del modelo para poder usarlos en el código, use estos vínculos para ir directamente a los archivos de GitHub. A continuación, haga clic con el botón derecho en cualquier lugar de la pantalla, seleccione Guardar como en el menú contextual del explorador y use la ventana Guardar como para guardar los archivos como Room.json y Floor.json.

Después cree un nuevo proyecto de aplicación de consola en Visual Studio o el editor que prefiera.

Luego copie el código siguiente del ejemplo ejecutable en el proyecto:

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>
    }
}

Nota:

Actualmente hay un problema conocido que afecta a la clase contenedora DefaultAzureCredential que puede dar lugar a un error durante la autenticación. Si se produce este problema, puede intentar crear instancias de DefaultAzureCredential con el siguiente parámetro opcional para resolverlo: new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true });

Para más información sobre este problema, consulte Problemas conocidos de Azure Digital Twins.

Configurar proyecto

Luego siga estos pasos para configurar el código del proyecto:

  1. Agregue los archivos Room.json y Floor.json que ha descargado anteriormente al proyecto y reemplace los marcadores de posición <path-to> en el código para indicar al programa dónde encontrarlos.

  2. Reemplace el marcador de posición <your-instance-hostname> por el nombre de host de la instancia de Azure Digital Twins.

  3. Agregue dos dependencias al proyecto ya que las necesitará para trabajar con Azure Digital Twins. La primera es el paquete del SDK de Azure Digital Twins para .NET y la segunda proporciona herramientas para ayudar con la autenticación en Azure.

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

También necesitará configurar las credenciales locales si desea ejecutar el ejemplo directamente. En la sección siguiente se indica cómo realizar este proceso.

Configuración de credenciales locales de Azure

En este ejemplo se usa DefaultAzureCredential (parte de la biblioteca de Azure.Identity) para autenticar a los usuarios mediante la instancia de Azure Digital Twins cuando la ejecuta en la máquina local. Para más información sobre las distintas formas en que una aplicación cliente puede autenticarse con Azure Digital Twins, consulte Escritura de código de autenticación de aplicación.

Con DefaultAzureCredential, el ejemplo buscará las credenciales en el entorno local; por ejemplo, un inicio de sesión de Azure en una DefaultAzureCredential local o en Visual Studio o Visual Studio Code. Por este motivo, debe iniciar sesión en Azure localmente mediante uno de estos mecanismos para configurar las credenciales del ejemplo.

Si usa Visual Studio o Visual Studio Code para ejecutar códigos de ejemplo, asegúrese de que inicia sesión en ese editor con las mismas credenciales de Azure que quiere usar para acceder a la instancia de Azure Digital Twins. Si usa una ventana local de la CLI, ejecute el comando az login para iniciar sesión en la cuenta de Azure. Después de ejecutar el código de ejemplo, debería autenticarse automáticamente.

Ejecución del ejemplo

Ahora que ha completado la configuración, puede ejecutar el proyecto de código de ejemplo.

Esta es la salida de la consola del programa:

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

Sugerencia

El grafo de gemelos es un concepto de creación de relaciones entre gemelos. Si desea ver la representación visual del grafo de gemelos, consulte la sección Visualización de este artículo.

Pasos siguientes

Obtenga información sobre cómo consultar un grafo de gemelos de Azure Digital Twins: