デジタル ツインを管理する

環境内のエンティティは、デジタル ツインで表されます。 デジタル ツインの管理には、作成、変更、削除が含まれる場合があります。

この記事では、デジタル ツインの管理に重点を置いて説明します。リレーションシップとツイン グラフの全体的な操作については、「ツイン グラフとリレーションシップを管理する」をご覧ください。

ヒント

すべての SDK 関数に同期バージョンと非同期バージョンがあります。

前提条件

この記事で Azure Digital Twins を操作するには、まず Azure Digital Twins インスタンスとそれを使用するために必要なアクセス許可が必要です。 Azure Digital Twins インスタンスが既に設定されている場合は、そのインスタンスを使用し、次のセクションに進むことができます。 それ以外の場合は、「インスタンスと認証を設定する」の手順に従います。 手順には、各ステップを正常に完了したことを確認するために役立つ情報が含まれています。

インスタンスの設定後、インスタンスのホスト名を書き留めておきます。 ホスト名は Azure portal で確認できます。

開発者インターフェイス

この記事では、.NET (C#) SDK を使用して、さまざまな管理操作を実行する方法について説明します。 また、「Azure Digital Twins API および SDK」で説明されているその他の言語の SDK を使用して、これらと同じ管理呼び出しを作成することもできます。

これらの操作を完了するために使用できるその他の開発者インターフェイスは次のとおりです。

ビジュアル化

Azure Digital Twins Explorer は、Azure Digital Twins グラフ内のデータを探索するためのビジュアル ツールです。 エクスプローラーを使用して、モデル、ツイン、リレーションシップを表示、クエリ、編集できます。

Azure Digital Twins Explorer ツールについて詳しくは、Azure Digital Twins Explorer に関するページを参照してください。 その機能を使用する方法の詳細な手順については、Azure Digital Twins Explorer を使用するに関するページを参照してください。

視覚化は次のように表示されます。

サンプルのモデルとツインが表示されている Azure Digital Twins Explorer のスクリーンショット。

デジタル ツインを作成する

ツインを作成するには、次のようにサービス クライアントで CreateOrReplaceDigitalTwinAsync() メソッドを使用します。

await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(twinId, initData);

デジタル ツインを作成するには、以下を指定する必要があります。

  • デジタル ツインに割り当てる ID 値 (ツインの作成時にその ID を定義する)
  • 使用するモデル
  • 任意のツイン データの必要な初期化。次を含みます
    • プロパティ (初期化オプション): 必要に応じて、デジタル ツインのプロパティの初期値を設定できます。 プロパティはオプションとして扱われ、後で設定できますが、設定されるまではツインの一部として表示されません
    • コンポーネント (ツインに存在する場合は初期化が必要): ツインにコンポーネントが含まれている場合は、ツインの作成時に初期化する必要があります。 空のオブジェクトにすることができますが、コンポーネント自体は存在する必要があります。

モデルと初期プロパティ値は、initData パラメーターによって提供されます。これは、関連データを含む JSON 文字列です。 このオブジェクトを構造化する方法の詳細については、次のセクションに進んでください。

ヒント

ツインを作成または更新した後、変更がクエリに反映されるまでに最大 10 秒の待機時間が発生する可能性があります。 GetDigitalTwin API (この記事で後ほど説明します) ではこの遅延が発生しないため、迅速な応答が必要な場合は、クエリの代わりに API 呼び出しを使用して、新しく作成されたツインを確認してください。

モデルとプロパティを初期化する

ツインの作成時、ツインのプロパティを初期化できます。

ツイン作成 API は、ツイン プロパティの有効な JSON 記述にシリアル化されるオブジェクトを受け入れます。 ツインの JSON 形式の説明については、デジタル ツインとツイン グラフに関する記事をご覧ください。

まず、ツインとそのプロパティ データを表すデータ オブジェクトを作成することができます。 パラメーター オブジェクトは手動で作成することも、用意されているヘルパー クラスを使用して作成することもできます。 それぞれの例を次に示します。

手動で作成したデータを使用してツインを作成する

カスタム ヘルパー クラスを使用しない場合は、Dictionary<string, object> でツインのプロパティを表すことができます。ここで、string はプロパティの名前であり、object はプロパティとその値を表すオブジェクトです。

// Define a custom model type for the twin to be created

internal class CustomDigitalTwin
{
    [JsonPropertyName(DigitalTwinsJsonPropertyNames.DigitalTwinId)]
    public string Id { get; set; }

    [JsonPropertyName(DigitalTwinsJsonPropertyNames.DigitalTwinETag)]
    public string ETag { get; set; }

    [JsonPropertyName("temperature")]
    public double Temperature { get; set; }

    [JsonPropertyName("humidity")]
    public double Humidity{ get; set; }
}

// Initialize properties and create the twin
public class TwinOperationsCreateTwin
{
    public async Task CreateTwinAsync(DigitalTwinsClient client)
    {
        // Initialize the twin properties
        var myTwin = new CustomDigitalTwin
        {
            Temperature = 25.0,
            Humidity = 50.0,
        };

        // Create the twin
        const string twinId = "<twin-ID>";
        Response<CustomDigitalTwin> response = await client.CreateOrReplaceDigitalTwinAsync(twinId, myTwin);
        Console.WriteLine($"Temperature value: {response.Value.Temperature}");
    }
}

ヘルパー クラスを使用してツインを作成する

BasicDigitalTwin のヘルパー クラスを使用すると、"ツイン" オブジェクトにプロパティ フィールドを直接的に格納できます。 それでも、Dictionary<string, object> を使ってプロパティのリストを作成したい場合があります。そうすれば、CustomProperties としてツイン オブジェクトに直接追加できます。

string twinId = "myTwinID";
var initData = new BasicDigitalTwin
{
    Id = twinId,
    Metadata = { ModelId = "dtmi:example:Room;1" },
    // Initialize properties
    Contents =
    {
        { "Temperature", 25.0 },
        { "Humidity", 50.0 },
    },
};

await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(twinId, initData);

Note

BasicDigitalTwin オブジェクトには、Id フィールドが付属しています。 このフィールドは空のままとすることができますが、ID 値を追加する場合は、それが、CreateOrReplaceDigitalTwinAsync() 呼び出しに渡される ID パラメーターと一致する必要があります。 次に例を示します。

twin.Id = "myRoomId";

Import Jobs API を使用してツインを一括で作成する

Import Jobs API を使うと、1 回の API 呼び出しで多数のツインを一度に作成できます。 この方法では、Azure Blob Storage を使う必要があり、ツインと一括ジョブに対する Azure Digital Twins インスタンスでの書き込みアクセス許可が必要です。

ヒント

Import Jobs API を使うと、同じ呼び出しでモデルとリレーションシップをインポートし、グラフのすべての部分を一度に作成することもできます。 このプロセスの詳細については、「Import Jobs API を使用してモデル、ツイン、リレーションシップを一括でアップロードする」を参照してください。

ツインを一括でインポートするには、ツイン (および一括インポート ジョブに含まれる他のリソース) を NDJSON ファイルとして構成する必要があります。 Twins セクションは、Models セクションの後 (Relationships セクションの前) に置きます。 ファイルで定義されているツインでは、このファイルで定義されている、またはインスタンスに既に存在するモデルを参照でき、必要に応じてツインのプロパティの初期化を含むことができます。

インポート ファイルの例と、これらのファイルの作成に関するサンプル プロジェクトについては、Import Jobs API の紹介に関する記事で参照できます。

次に、Azure Blob Storage 内の追加 BLOB にファイルをアップロードする必要があります。 Azure ストレージ コンテナーを作成する方法の手順については、「コンテナーを作成する」を参照してください。 次に、ご希望のアップロード方法を使用してファイルをアップロードします (AzCopy コマンドAzure CLIAzure portal などのオプションがあります)。

NDJSON ファイルがコンテナーにアップロードされたら、BLOB コンテナー内で URL を取得します。 この値は、後で一括インポート API 呼び出しの本文で使用します。

Azure portal 内の BLOB ファイルの URL 値のスクリーンショットを次に示します。

ストレージ コンテナー内のファイルの URL を示す Azure portal のスクリーンショット。

その後、Import Jobs API 呼び出しでこのファイルを使用できます。 入力ファイルの Blob Storage の URL と、サービスで出力ログが作成された後にそれを格納する場所を示す新しい Blob Storage の URL を指定します。

デジタル ツインのデータを取得する

次のように GetDigitalTwin() メソッドを呼び出すことで、デジタル ツインの詳細にアクセスすることができます。

Response<BasicDigitalTwin> twinResponse = await client.GetDigitalTwinAsync<BasicDigitalTwin>(twinId);
twin = twinResponse.Value;

この呼び出しからは、ツイン データが BasicDigitalTwin のような厳密に型指定されたオブジェクト型として返されます。 BasicDigitalTwin は、SDK に含まれるシリアル化ヘルパー クラスであり、ツインのコア メタデータとプロパティを解析済みの形式で返します。 System.Text.JsonNewtonsoft.Json といった任意の JSON ライブラリを使用して、ツイン データをいつでも逆シリアル化できます。 ただし、ツインへの基本的なアクセスについては、ヘルパー クラスを使用すると便利です。

Note

BasicDigitalTwin では System.Text.Json 属性が使用されます。 BasicDigitalTwinDigitalTwinsClient で使用するためには、既定のコンストラクターを使用してクライアントを初期化する必要があります。または、シリアライザー オプションをカスタマイズする場合は、JsonObjectSerializer を使用します。

また、BasicDigitalTwin ヘルパー クラスを使用すると、ツインで定義されたプロパティに Dictionary<string, object> を介してアクセスできます。 ツインのプロパティを一覧表示するには、次のコードを使用します。

BasicDigitalTwin twin;
Response<BasicDigitalTwin> twinResponse = await client.GetDigitalTwinAsync<BasicDigitalTwin>(twinId);
twin = twinResponse.Value;
Console.WriteLine($"Model id: {twin.Metadata.ModelId}");
foreach (string prop in twin.Contents.Keys)
{
    if (twin.Contents.TryGetValue(prop, out object value))
        Console.WriteLine($"Property '{prop}': {value}");
}

GetDigitalTwin() メソッドを使用してツインを取得すると、少なくとも 1 回は設定されたプロパティだけが返されます。

ヒント

ツインの displayName はモデル メタデータの一部であるため、ツイン インスタンスのデータを取得するときには表示されません。 この値を表示するには、モデルから取得することができます。

1 回の API 呼び出しで複数のツインを取得するには、ツイン グラフのクエリに関する記事で Query API の例を参照してください。

Moon を定義する次のモデル (Digital Twins Definition Language (DTDL) で記述) について考えてみましょう。

{
    "@id": "dtmi:example:Moon;1",
    "@type": "Interface",
    "@context": "dtmi:dtdl:context;3",
    "contents": [
        {
            "@type": "Property",
            "name": "radius",
            "schema": "double",
            "writable": true
        },
        {
            "@type": "Property",
            "name": "mass",
            "schema": "double",
            "writable": true
        }
    ]
}

Moon 型ツインで object result = await client.GetDigitalTwinAsync("my-moon"); を呼び出すと、結果は次のようになります。

{
  "$dtId": "myMoon-001",
  "$etag": "W/\"e59ce8f5-03c0-4356-aea9-249ecbdc07f9\"",
  "radius": 1737.1,
  "mass": 0.0734,
  "$metadata": {
    "$model": "dtmi:example:Moon;1",
    "radius": {
      "lastUpdateTime": "2022-12-06T20:00:32.8209188Z"
    },
    "mass": {
      "lastUpdateTime": "2022-12-04T12:04:43.3859361Z"
    }
  }
}

デジタル ツインの定義済みプロパティは、デジタル ツインの最上位プロパティとして返されます。 DTDL 定義に含まれていないメタデータやシステム情報は、$ プレフィックス付きで返されます。 メタデータ プロパティには以下の値が含まれます。

  • $dtId: この Azure Digital Twins インスタンスのデジタル ツインの ID
  • $etag: Web サーバーによって割り当てられた標準 HTTP フィールド。 これはツインが更新されるたびに新しい値に更新されます。これは、前回のチェックからサーバー上でツインのデータが更新されているかどうかを判断するのに役立ちます。 If-Match を使用すると、 エンティティの etag が指定された etag と一致する場合にのみ完了する更新と削除を実行できます。 これらの操作の詳細については、 DigitalTwins UpdateDigitalTwins Deleteのドキュメントを参照してください。
  • $metadata: メタデータ プロパティのセット。次のものが含まれる場合があります。
    • $model。デジタル ツインのモデルの DTMI。
    • ツイン プロパティの lastUpdateTime。 これは、Azure Digital Twins がプロパティ更新メッセージを処理した日時を示すタイムスタンプです
    • ツイン プロパティの sourceTime。 これは、プロパティの更新が実際に観察されたタイムスタンプを表す、省略可能で書き込み可能なプロパティです。

デジタル ツインに含まれるフィールドについて詳しくは、「デジタル ツインの JSON 形式」をご覧ください。 BasicDigitalTwin などのシリアル化ヘルパー クラスの詳細については、「Azure Digital Twins API と SDK」で参照してください。

すべてのデジタル ツインを表示する

インスタンス内のすべてのデジタル ツインを表示するには、クエリを使用します。 クエリは、Query API または CLI コマンドを使用して実行できます。

次に示すのは、インスタンス内のすべてのデジタル ツインの一覧を返す基本的なクエリの本体です。

SELECT * FROM DIGITALTWINS

デジタル ツインを更新する

デジタル ツインのプロパティを更新するには、置き換える情報を JSON Patch 形式で書き込みます。 replaceaddremove などの使用可能な JSON Patch 操作の詳細なリストについては、「JSON Patch の操作」を参照してください。

更新情報を含む JSON Patch ドキュメントを作成した後、ドキュメントを UpdateDigitalTwin() メソッドに渡します。

await client.UpdateDigitalTwinAsync(twinId, updateTwinData);

パッチを 1 度呼び出すだけで、1 つのツインのプロパティを必要な数だけ (すべてであっても) 更新することができます。 複数のツインを対象にプロパティを更新する必要がある場合は、ツインごとに個別に更新を呼び出す必要があります。

ヒント

ツインを作成または更新した後、変更がクエリに反映されるまでに最大 10 秒の待機時間が発生する可能性があります。 GetDigitalTwin API (この記事の前の方で説明しました) ではこの遅延が発生しないため、迅速な応答が必要な場合は、クエリの代わりに API 呼び出しを使用して、新しく更新されたツインを確認してください。

JSON Patch コードの例を次に示します。 このドキュメントでは、適用先のデジタル ツインの mass および radius プロパティ値を置き換えます。 この例では、既存のプロパティの値を置き換える JSON Patch replace 操作を示しています。

[
    {
      "op": "replace",
      "path": "/mass",
      "value": 0.0799
    },
    {
      "op": "replace",
      "path": "/radius",
      "value": 0.800
    }
  ]

.NET SDK を使用してコードプロジェクトからツインを更新する場合は、Azure .NET SDK の Jsonpatchdocument を使用して JSON 修正プログラムを作成できます。 JSON Patch ドキュメントを作成し、プロジェクト コードで UpdateDigitalTwin() を使用する例を次に示します。

var updateTwinData = new JsonPatchDocument();
updateTwinData.AppendAdd("/Temperature", 25.0);
updateTwinData.AppendAdd("/myComponent/Property", "Hello");
// Un-set a property
updateTwinData.AppendRemove("/Humidity");

await client.UpdateDigitalTwinAsync("myTwin", updateTwinData).ConfigureAwait(false);

ヒント

このセクションで説明するプロセスを使用して $metadata.<property-name>.sourceTime フィールドを更新することで、デジタル ツインのソース タイムスタンプを維持できます。 このフィールドとデジタル ツインで書き込み可能なその他のフィールドの詳細については、「デジタル ツインの JSON 形式」を参照してください。

デジタル ツイン コンポーネントのサブプロパティを更新する

モデルには、他のモデルで構成できるコンポーネントが含まれている場合があることに注意してください。

デジタル ツインのコンポーネントのプロパティにパッチを適用するには、JSON Patch で path 構文を使用することができます。

[
  {
    "op": "replace",
    "path": "/mycomponentname/mass",
    "value": 0.0799
  }
]

オブジェクト型プロパティのサブプロパティを更新する

モデルには、オブジェクト型のプロパティが含まれている場合があります。 それらのオブジェクトは独自のプロパティを持つ場合があり、オブジェクト型プロパティに属するそれらのサブプロパティの 1 つを更新したい場合があります。 このプロセスは、コンポーネントのサブプロパティを更新するプロセスに似ていますが、追加の手順が必要な場合があります。

オブジェクト型プロパティ ObjectProperty を持つモデルについて検討してみましょう。 ObjectProperty には StringSubProperty という名前の文字列プロパティがあります。

このモデルを使用してツインを作成する場合、その時点で ObjectProperty をインスタンス化する必要はありません。 ツインの作成中にオブジェクト プロパティがインスタンス化されない場合、パッチ操作用の ObjectProperty とその StringSubProperty にアクセスするための既定のパスは作成されません。 プロパティを更新する前に、ObjectProperty へのパスを自分で追加する必要があります。

これは、次のように JSON Patch add 操作 を使用して実行できます。

[
  {
    "op": "add", 
    "path": "/ObjectProperty", 
    "value": {"StringSubProperty":"<string-value>"}
  }
]

Note

ObjectProperty に複数のプロパティがある場合は、更新するものが 1 つだけのときでも、この操作の value フィールドにそれらすべてを含める必要があります。

... "value": {"StringSubProperty":"<string-value>", "Property2":"<property2-value>", ...}

これが 1 回実行された後は、StringSubProperty へのパスが存在するため、以降は通常の replace 操作で直接更新できます。

[
  {
    "op": "replace",
    "path": "/ObjectProperty/StringSubProperty",
    "value": "<string-value>"
  }
]

最初のステップは、ツインの作成時に ObjectProperty がインスタンス化された場合は必要ありませんが、オブジェクト プロパティが最初にインスタンス化されたかどうかが常にわかるとは限らないので、サブプロパティを初めて更新するたびにそれを使うことをお勧めします。

デジタル ツインのモデルを更新する

UpdateDigitalTwin() 関数を使用して、デジタル ツインを別のモデルに移行することもできます。

たとえば、デジタル ツインのメタデータの $model フィールドを置き換える、次の JSON Patch ドキュメントについて考えてみましょう。

[
  {
    "op": "replace",
    "path": "/$metadata/$model",
    "value": "dtmi:example:foo;1"
  }
]

この操作は、パッチによって変更されるデジタル ツインが新しいモデルに適合する場合にのみ成功します。

次の例を確認してください。

  1. foo_old というモデルを使用するデジタル ツインがあるとします。 foo_old では、必須のプロパティ mass が定義されています。
  2. 新しいモデル foo_new では、mass プロパティが定義され、新しい必須のプロパティ temperature が追加されています。
  3. パッチの適用後、このデジタル ツインには mass と temperature の両方のプロパティが含まれている必要があります。

この場合のパッチでは、次のように、モデルとツインの temperature プロパティを両方とも更新する必要があります。

[
  {
    "op": "replace",
    "path": "/$metadata/$model",
    "value": "dtmi:example:foo_new;1"
  },
  {
    "op": "add",
    "path": "/temperature",
    "value": 60
  }
]

プロパティの sourceTime を更新する

必要に応じて、ツイン プロパティの sourceTime フィールドを使って、実際にプロパティの更新が観察された日時のタイムスタンプを記録できます。 Azure Digital Twins は、各ツイン プロパティのメタデータの sourceTime をネイティブにサポートします。 sourceTime 値は、ISO 8601 の日付と時刻の形式に準拠している必要があります。 このフィールドとデジタル ツインのその他のフィールドの詳細については、「デジタル ツインの JSON 形式」を参照してください。

このフィールドをサポートする REST API の最小の安定バージョンは、2022-05-31 バージョンです。 Azure Digital Twins SDK を使用してこのフィールドを操作するには、最新バージョンの SDK を使用して、このフィールドが含まれていることを確認することをお勧めします。

Temperature プロパティの値と sourceTime フィールドの両方を更新する JSON Patch ドキュメントの例を次に示します。

[
  {
    "op": "replace",
    "path": "/Temperature",
    "value": "22.3"
  },
  {
    "op": "replace",
    "path": "/$metadata/Temperature/sourceTime",
    "value": "2021-11-30T18:47:53.7648958Z"
  }
]

コンポーネントの一部であるプロパティの sourceTime フィールドを更新するには、パスの最初にコンポーネントを含める必要があります。 上の例では、パス値を /$metadata/Temperature/sourceTime から myComponent/$metadata/Temperature/sourceTime に変更することでこれを実施しています。

Note

sourceTime とプロパティの値の両方を更新し、その後、プロパティの値だけを更新した場合、最初の更新からの sourceTime タイムスタンプが残ります。

競合する更新呼び出しを処理する

Azure Digital Twins では、すべての受信要求が確実に 1 つずつ処理されます。 つまり、複数の関数が同時にツイン上の同じプロパティを更新しようとする場合でも、競合を処理するための明示的なロック コードを記述する必要はありません

この動作はツインごとに行われます。

例として、これら 3 つの呼び出しが同時に到着するシナリオを考えてみましょう。

  • Twin1 でのプロパティ A の書き込み
  • Twin1 でのプロパティ B の書き込み
  • Twin2 でのプロパティ A の書き込み

Twin1 を変更する 2 つの呼び出しが 1 つずつ実行され、変更のたびに変更メッセージが生成されます。 Twin2 を変更する呼び出しは、到着したらすぐに、競合なしで同時に実行できます。

デジタル ツインを削除する

DeleteDigitalTwin() メソッドを使用してツインを削除することができます。 ただし、ツインを削除できるのは、ツインにリレーションシップがない場合だけです。 そのため、最初にツインの受信と送信のリレーションシップを削除します。

次に示すのは、ツインとそのリレーションシップを削除するコードの例です。 DeleteDigitalTwin SDK の呼び出しが強調表示されており、この例の幅広いコンテキストでの位置が明確になっています。

private static async Task CustomMethod_DeleteTwinAsync(DigitalTwinsClient client, string twinId)
{
    await CustomMethod_FindAndDeleteOutgoingRelationshipsAsync(client, twinId);
    await CustomMethod_FindAndDeleteIncomingRelationshipsAsync(client, twinId);
    try
    {
        await client.DeleteDigitalTwinAsync(twinId);
        Console.WriteLine("Twin deleted successfully");
    }
    catch (RequestFailedException ex)
    {
        Console.WriteLine($"*** Error:{ex.Message}");
    }
}

private static async Task CustomMethod_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}");
    }
}

private static async Task CustomMethod_FindAndDeleteIncomingRelationshipsAsync(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);

        await foreach (IncomingRelationship incomingRel in incomingRels)
        {
            await client.DeleteRelationshipAsync(incomingRel.SourceId, incomingRel.RelationshipId).ConfigureAwait(false);
            Console.WriteLine($"Deleted incoming relationship {incomingRel.RelationshipId} from {dtId}");
        }
    }
    catch (RequestFailedException ex)
    {
        Console.WriteLine($"*** Error {ex.Status}/{ex.ErrorCode} retrieving or deleting incoming relationships for {dtId} due to {ex.Message}");
    }
}

すべてのデジタル ツインを削除する

一度にすべてのツインを削除する方法の例については、サンプル クライアント アプリを使用した基本事項の確認に関するページで使用されているサンプル アプリをダウンロードしてください。 CommandLoop.cs ファイルでは、CommandDeleteAllTwins() 関数でこれを実行します。

Note

インスタンス内のすべてのモデル、ツイン、リレーションシップを一度に削除する場合は、Delete Jobs API を使用します。

実行可能なデジタル ツインのコード サンプル

次の実行可能なコード サンプルを使用してツインを作成し、その詳細を更新して、ツインを削除することができます。

サンプル プロジェクト ファイルの設定

このスニペットでは、サンプル モデル定義 Room.json を使用します。 モデル ファイルをダウンロードしてコードで使用できるようにするには、このリンクを使用して GitHub のファイルに直接アクセスします。 次に、画面上の任意の場所を右クリックし、ブラウザーの右クリック メニューから [名前を付けて保存] を選択して、[名前を付けて保存] のウィンドウでファイルを Room.json で保存します。

次に、Visual Studio または任意のエディターで、新しいコンソール アプリ プロジェクトを作成します。

次に、実行可能なサンプルの次のコードをプロジェクトにコピーします。

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

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

            // Create the Azure Digital Twins client for API calls
            string adtInstanceUrl = "https://<your-instance-hostname>";
            var credentials = new DefaultAzureCredential();
            var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), credentials);
            Console.WriteLine($"Service client created – ready to go");

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

            // Create new digital twin
            // <CreateTwin_withHelper>
            string twinId = "myTwinID";
            var initData = new BasicDigitalTwin
            {
                Id = twinId,
                Metadata = { ModelId = "dtmi:example:Room;1" },
                // Initialize properties
                Contents =
                {
                    { "Temperature", 25.0 },
                    { "Humidity", 50.0 },
                },
            };

            // <CreateTwinCall>
            await client.CreateOrReplaceDigitalTwinAsync<BasicDigitalTwin>(twinId, initData);
            // </CreateTwinCall>
            // </CreateTwin_withHelper>
            Console.WriteLine("Twin created successfully");

            //Print twin
            Console.WriteLine("--- Printing twin details:");
            await CustomMethod_FetchAndPrintTwinAsync(twinId, client);
            Console.WriteLine("--------");

            //Update twin data
            var updateTwinData = new JsonPatchDocument();
            updateTwinData.AppendAdd("/Temperature", 30.0);
            // <UpdateTwinCall>
            await client.UpdateDigitalTwinAsync(twinId, updateTwinData);
            // </UpdateTwinCall>
            Console.WriteLine("Twin properties updated");
            Console.WriteLine();

            //Print twin again
            Console.WriteLine("--- Printing twin details (after update):");
            await CustomMethod_FetchAndPrintTwinAsync(twinId, client);
            Console.WriteLine("--------");
            Console.WriteLine();

            //Delete twin
            await CustomMethod_DeleteTwinAsync(client, twinId);
        }

        private static async Task<BasicDigitalTwin> CustomMethod_FetchAndPrintTwinAsync(string twinId, DigitalTwinsClient client)
        {
            // <GetTwin>
            BasicDigitalTwin twin;
            // <GetTwinCall>
            Response<BasicDigitalTwin> twinResponse = await client.GetDigitalTwinAsync<BasicDigitalTwin>(twinId);
            twin = twinResponse.Value;
            // </GetTwinCall>
            Console.WriteLine($"Model id: {twin.Metadata.ModelId}");
            foreach (string prop in twin.Contents.Keys)
            {
                if (twin.Contents.TryGetValue(prop, out object value))
                    Console.WriteLine($"Property '{prop}': {value}");
            }
            // </GetTwin>

            return twin;
        }

        // <DeleteTwin>
        private static async Task CustomMethod_DeleteTwinAsync(DigitalTwinsClient client, string twinId)
        {
            await CustomMethod_FindAndDeleteOutgoingRelationshipsAsync(client, twinId);
            await CustomMethod_FindAndDeleteIncomingRelationshipsAsync(client, twinId);
            try
            {
                await client.DeleteDigitalTwinAsync(twinId);
                Console.WriteLine("Twin deleted successfully");
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"*** Error:{ex.Message}");
            }
        }

        private static async Task CustomMethod_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}");
            }
        }

        private static async Task CustomMethod_FindAndDeleteIncomingRelationshipsAsync(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);

                await foreach (IncomingRelationship incomingRel in incomingRels)
                {
                    await client.DeleteRelationshipAsync(incomingRel.SourceId, incomingRel.RelationshipId).ConfigureAwait(false);
                    Console.WriteLine($"Deleted incoming relationship {incomingRel.RelationshipId} from {dtId}");
                }
            }
            catch (RequestFailedException ex)
            {
                Console.WriteLine($"*** Error {ex.Status}/{ex.ErrorCode} retrieving or deleting incoming relationships for {dtId} due to {ex.Message}");
            }
        }
        // </DeleteTwin>

    }
}

Note

現在 DefaultAzureCredential ラッパー クラスに影響を与える既知の問題があり、それが原因で認証中にエラーが発生する可能性があります。 この問題が発生した場合は、次の省略可能なパラメーターを使用して DefaultAzureCredential のインスタンス化を試して解決することができます: new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeSharedTokenCacheCredential = true });

この問題の詳細については、Azure Digital Twins の既知の問題に関する記事を参照してください。

プロジェクトを構成する

次に、以下の手順を実行してプロジェクト コードを構成します。

  1. 以前ダウンロードした Room.json ファイルをプロジェクトに追加し、コード内部の <path-to> プレースホルダーを置き換えて、プログラムに検索する場所を指示します。

  2. プレースホルダー <your-instance-hostname> を Azure Digital Twins インスタンスのホスト名に置き換えます。

  3. Azure Digital Twins を操作するために必要な 2 つの依存関係をプロジェクトに追加します。 1 つ目は .NET 用 Azure Digital Twins SDK 用のパッケージであり、2 つ目では Azure に対する認証に役立つツールが提供されます。

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

サンプルを直接実行する場合は、ローカルの資格情報を設定する必要もあります。 次のセクションでは、これについて説明します。

ローカルの Azure 資格情報を設定する

このサンプルでは、ローカル コンピューターで実行された Azure Digital Twins インスタンスに対し、(Azure.Identity ライブラリの) DefaultAzureCredential を使用してユーザーを認証します。 Azure Digital Twins に対してクライアント アプリの認証を行う各種の方法について詳しくは、「アプリ認証コードを作成する」を参照してください。

このサンプルでは、DefaultAzureCredential を使用して、ローカル環境から資格情報が検索されます。たとえば、ローカルの DefaultAzureCredential や Visual Studio または Visual Studio Code での Azure サインインなどです。 このため、サンプルの資格情報を設定するために、これらのメカニズムのいずれかを使用して、Azure にローカルでサインインする必要があります。

Visual Studio または Visual Studio Code を使用してコード サンプルを実行する場合は、Azure Digital Twins インスタンスへのアクセスに使用する Azure 資格情報でそのエディターにサインインしていることを確認してください。 ローカル CLI ウィンドウを使用している場合は、az login コマンドを実行して Azure アカウントにサインインします。 その後、コード サンプルを実行すれば、自動的に認証処理が行われます。

サンプルを実行する

セットアップが完了したので、サンプル コード プロジェクトを実行できます。

上のプログラムのコンソール出力は次のようになります。

ツインが作成、更新、削除されたことを示すコンソール出力のスクリーンショット。

次のステップ

デジタル ツイン間のリレーションシップを作成および管理する方法を確認します。