直接写入到存储

适用于:SDK v4

可以对存储对象进行直接读写,不需使用中间件或上下文对象。 这可能适用于机器人用来保留聊天的数据,或者来源于机器人聊天流外部的数据。 在此数据存储模型中,直接从存储中读取数据,而不使用状态管理器。 本文中的代码示例演示如何使用内存存储Cosmos DBAzure BlobAzure Blob 记录对存储进行数据读写。

注意

Bot Framework JavaScript、C# 和 Python SDK 将继续受支持,但 Java SDK 即将停用,最终长期支持将于 2023 年 11 月结束。

使用 Java SDK 构建的现有机器人将继续正常运行。

要生成新的机器人,请考虑使用 Microsoft Copilot Studio 并阅读选择正确的助理解决方案

有关详细信息,请参阅机器人构建的未来

先决条件

注意

可以在 Visual Studio 中安装模板。

  1. 在菜单中,选择扩展,然后选择管理扩展
  2. 管理扩展对话框中,搜索并安装 Bot Framework v4 SDK templates for Visual Studio

有关将 .NET 机器人部署到 Azure 的信息,请参阅如何预配和发布机器人

关于此示例

本文中的示例代码首先演示基本聊天机器人的结构,然后通过添加更多的代码(下面会提供)来扩展该机器人的功能。 此扩展代码会创建一个列表,用于在收到用户输入时保留这些输入。 在每个轮次,会将保存到内存的完整用户输入列表回显给用户。 然后会将包含此输入列表的数据结构进行修改,以保存到存储中。 作为附加功能探讨的各种存储将添加到此示例代码。

内存存储

Bot Framework SDK 允许使用内存中存储来存储用户输入。 由于每次重启机器人时都会清除内存存储,因此它最适合用于测试目的,不适用于生产用途。 持久性存储类型(例如数据库存储)最适合生产用机器人。

生成基础机器人

本主题的其余部分在 Echo 机器人的基础上进行构建。 可以按照创建机器人快速入门说明,在本地生成回显机器人示例代码。

EchoBot.cs 中的代码替换为以下代码:

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

// Represents a bot saves and echoes back user input.
public class EchoBot : ActivityHandler
{
   // Create local Memory Storage.
   private static readonly MemoryStorage _myStorage = new MemoryStorage();

   // Create cancellation token (used by Async Write operation).
   public CancellationToken cancellationToken { get; private set; }

   // Class for storing a log of utterances (text of messages) as a list.
   public class UtteranceLog : IStoreItem
   {
      // A list of things that users have said to the bot
      public List<string> UtteranceList { get; } = new List<string>();

      // The number of conversational turns that have occurred
      public int TurnNumber { get; set; } = 0;

      // Create concurrency control where this is used.
      public string ETag { get; set; } = "*";
   }

   // Echo back user input.
   protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
   {
      // preserve user input.
      var utterance = turnContext.Activity.Text;

      // Make empty local log-items list.
      UtteranceLog logItems = null;

      // See if there are previous messages saved in storage.
      try
      {
         string[] utteranceList = { "UtteranceLog" };
         logItems = _myStorage.ReadAsync<UtteranceLog>(utteranceList).Result?.FirstOrDefault().Value;
      }
      catch
      {
         // Inform the user an error occurred.
         await turnContext.SendActivityAsync("Sorry, something went wrong reading your stored messages!");
      }

      // If no stored messages were found, create and store a new entry.
      if (logItems is null)
      {
         // Add the current utterance to a new object.
         logItems = new UtteranceLog();
         logItems.UtteranceList.Add(utterance);

         // Set initial turn counter to 1.
         logItems.TurnNumber++;

         // Show user new user message.
         await turnContext.SendActivityAsync($"{logItems.TurnNumber}: The list is now: {string.Join(", ", logItems.UtteranceList)}");

         // Create dictionary object to hold received user messages.
         var changes = new Dictionary<string, object>();
         {
            changes.Add("UtteranceLog", logItems);
         }
         try
         {
            // Save the user message to your Storage.
            await _myStorage.WriteAsync(changes, cancellationToken);
         }
         catch
         {
            // Inform the user an error occurred.
            await turnContext.SendActivityAsync("Sorry, something went wrong storing your message!");
         }
      }
      // Else, our storage already contained saved user messages, add new one to the list.
      else
      {
         // add new message to list of messages to display.
         logItems.UtteranceList.Add(utterance);
         // increment turn counter.
         logItems.TurnNumber++;

         // show user new list of saved messages.
         await turnContext.SendActivityAsync($"{logItems.TurnNumber}: The list is now: {string.Join(", ", logItems.UtteranceList)}");

         // Create Dictionary object to hold new list of messages.
         var changes = new Dictionary<string, object>();
         {
            changes.Add("UtteranceLog", logItems);
         };

         try
         {
            // Save new list to your Storage.
            await _myStorage.WriteAsync(changes,cancellationToken);
         }
         catch
         {
            // Inform the user an error occurred.
            await turnContext.SendActivityAsync("Sorry, something went wrong storing your message!");
         }
      }
   }
}

启动机器人

在本地运行机器人。

启动模拟器并连接机器人

安装 Bot Framework Emulator。接下来,启动 Emulator,然后在 Emulator 中连接到机器人:

  1. 选择 Emulator“欢迎使用”选项卡中的“新建机器人配置”链接。
  2. 填写用于连接到机器人的字段,并指定启动机器人时要在网页上显示的信息。

与机器人交互

向机器人发送消息 机器人会列出它所收到的消息。

与机器人的对话,显示机器人保存了用户的消息列表。

本文的其余部分将演示如何保存到持久存储而不是机器人的内部内存。

使用 Cosmos DB

重要

Cosmos DB 存储类已弃用。 最初使用 CosmosDbStorage 创建的容器存储没有分区键集,并且被授予默认分区键 _/partitionKey

通过“Cosmos DB 存储”创建的容器可以与“Cosmos DB 分区存储”一起使用。 有关详细信息,请阅读 Azure Cosmos DB 中的分区

另请注意,与旧版 Cosmos DB 存储不同,Cosmos DB 分区存储不会自动在 Cosmos DB 帐户中创建数据库。 你需要手动创建新数据库,但需要跳过手动创建容器步骤,因为 CosmosDbPartitionedStorage 存储会为你创建容器。

使用内存存储以后,我们现在要更新代码,以便使用 Azure Cosmos DB。 Cosmos DB 是 Azure 的多区域分布式多模式数据库。 使用 Azure Cosmos DB 可跨任意数量的 Azure 地理区域弹性且独立地缩放吞吐量和存储。 它通过综合服务级别协议 (SLA) 提供吞吐量、延迟、可用性和一致性保证。

设置 Cosmos DB 资源

若要在机器人中使用 Cosmos DB,需在编写代码之前创建一个数据库资源。 有关 Cosmos DB 数据库和应用创建的深入说明,请参阅适用于 .NETNode.jsPython 的快速入门。

创建数据库帐户

  1. 转到 Azure 门户以创建 Azure Cosmos DB 帐户。 搜索并选择“Azure Cosmos DB”

  2. Azure Cosmos DB 页中,选择“新建”以显示“创建 Azure Cosmos DB 帐户”页。

    创建 Cosmos DB 帐户的屏幕截图。

  3. 提供以下字段的值:

    1. 订阅。 选择要用于此 Azure Cosmos 帐户的 Azure 订阅。
    2. 资源组。 选择现有资源组,或者选择“新建”并输入新资源组的名称。
    3. 帐户名。 输入标识此 Azure Cosmos 帐户的名称。 由于 documents.azure.cn 将追加到所提供的名称以创建 URI,因此,请使用唯一的名称。 注意以下准则:
      • 该名称在 Azure 中必须唯一。
      • 名称的长度必须介于 3 到 31 个字符之间。
      • 名称只能包含小写字母、数字和连字符 (-)。
    4. API。 选择 Core(SQL)
    5. Location。 选择离用户最近的位置,使他们能够以最快的速度访问数据。
  4. 选择“查看 + 创建” 。

  5. 通过验证后,请选择“创建”。

创建帐户需要几分钟时间。 等待门户中显示“祝贺你! 已创建 Azure Cosmos DB 帐户”页。

添加数据库

注意

不要自行创建容器。 机器人将在创建其内部 Cosmos DB 客户端时创建该容器,并确保正确对其进行配置,使之能够存储机器人状态。

  1. 在新建的 Cosmos DB 帐户中导航到“数据资源管理器”页,然后从“新建容器”下拉列表中选择“新建数据库”。 随后,在窗口的右侧会打开一个面板,你可以在其中输入新数据库的详细信息。

    创建 Cosmos DB 数据库的屏幕截图。

  2. 输入新数据库的 ID,并根据需要设置吞吐量(以后可以更改),最后选择“确定”以创建数据库。 请记下此数据库 ID,以便稍后在配置机器人时使用。

  3. 创建 Cosmos DB 帐户和数据库后,接下来需要复制某些值,以便能够将新数据库集成到机器人中。 若要检索这些值,请在 Cosmos DB 帐户的“数据库设置”部分导航到“密钥”选项卡。 在此页中,需要使用 URI(Cosmos DB 终结点)和 PRIMARY KEY(“授权密钥”)

现在,你应该有一个 Cosmos DB 帐户,其中包含一个数据库,并准备好在机器人设置中使用以下值。

  • URI
  • 主键
  • 数据库 ID

添加 Cosmos DB 配置信息

使用在本文前面部分记下的详细信息来设置终结点、授权密钥和数据库 ID。 最后,应该为在数据库中创建的用于存储机器人状态的容器选择一个适当的名称。 在下面的示例中,创建的 Cosmos DB 容器将命名为“bot-storage”。

在配置文件中添加以下信息。

appsettings.json

"CosmosDbEndpoint": "<your-CosmosDb-URI>",
"CosmosDbAuthKey": "<your-primary-key>",
"CosmosDbDatabaseId": "<your-database-id>",
"CosmosDbContainerId": "bot-storage"

安装 Cosmos DB 包

确保有 Cosmos DB 所需的包。

安装 Microsoft.Bot.Builder.Azure NuGet 包。 有关使用 NuGet 的详细信息,请参阅使用 NuGet 包管理器在 Visual Studio 中安装和管理包

Cosmos DB 实现

注意

版本 4.6 引入了新的 Cosmos DB 存储提供程序,即 Cosmos DB 分区存储类,并且弃用了原来的 Cosmos DB 存储类。 通过“Cosmos DB 存储”创建的容器可以与“Cosmos DB 分区存储”一起使用。 有关详细信息,请阅读 Azure Cosmos DB 中的分区

与旧的 Cosmos DB 存储不同,Cosmos DB 分区存储不会自动在 Cosmos DB 帐户中创建数据库。 你需要手动创建新数据库,但需要跳过手动创建容器步骤,因为 CosmosDbPartitionedStorage 存储会为你创建容器。

以下示例代码运行时,使用的是与上面提供的内存存储示例相同的机器人代码,差异之处于此列出。 以下代码段演示如何为“myStorage”实现 Cosmos DB 存储,替换本地内存存储。

首先需要更新 Startup.cs 以引用 Bot Builder Azure 库:

using Microsoft.Bot.Builder.Azure;

接下来,在 Startup.cs 中的 ConfigureServices 方法中,创建 CosmosDbPartitionedStorage 对象。 这将通过依赖项注入传递到 EchoBot 构造函数中。

// Use partitioned CosmosDB for storage, instead of in-memory storage.
services.AddSingleton<IStorage>(
    new CosmosDbPartitionedStorage(
        new CosmosDbPartitionedStorageOptions
        {
            CosmosDbEndpoint = Configuration.GetValue<string>("CosmosDbEndpoint"),
            AuthKey = Configuration.GetValue<string>("CosmosDbAuthKey"),
            DatabaseId = Configuration.GetValue<string>("CosmosDbDatabaseId"),
            ContainerId = Configuration.GetValue<string>("CosmosDbContainerId"),
            CompatibilityMode = false,
        }));

EchoBot.cs 中,将 _myStorage 变量声明 private static readonly MemoryStorage _myStorage = new MemoryStorage(); 更改为以下内容:

// variable used to save user input to CosmosDb Storage.
private readonly IStorage _myStorage;

然后向 EchoBot 构造函数传入 IStorage 对象:

public EchoBot(IStorage storage)
{
    if (storage is null) throw new ArgumentNullException();
    _myStorage = storage;
}

启动 Cosmos DB 机器人

在本地运行机器人。

使用 Bot Framework Emulator 测试 Cosmos DB 机器人

现在启动 Bot Framework Emulator 并连接到机器人:

  1. 选择 Emulator“欢迎使用”选项卡中的“新建机器人配置”链接。
  2. 填写用于连接到机器人的字段,并指定启动机器人时要在网页上显示的信息。

与 Cosmos DB 机器人交互

向机器人发送消息,机器人会列出所收到的消息。

与机器人的对话,显示机器人保存了用户的消息列表。

查看 Cosmos DB 数据

运行机器人并保存信息以后,即可在 Azure 门户的“数据资源管理器”选项卡下查看存储的数据。

Azure 门户中数据资源管理器的屏幕截图。

使用 Blob 存储

Azure Blob 存储是 Azure 的适用于云的对象存储解决方案。 Blob 存储最适合存储巨量的非结构化数据,例如文本或二进制数据。 本部分介绍如何创建 Azure Blob 存储帐户和容器,以及如何从机器人引用 Blob 存储容器。

有关 Blob 存储的更多信息,请参阅什么是 Azure Blob 存储?

创建 Blob 存储帐户

若要在机器人中使用 Blob 存储,需在编写代码之前进行一些设置。

  1. Azure 门户中,选择“所有服务”。

  2. 在“所有服务”页的“特别推荐”部分,选择“存储帐户”。

  3. 在“存储帐户”页上,选择“新建”

    创建 Azure 存储帐户的屏幕截图。

  4. 在“订阅”字段中,选择要在其中创建存储帐户的订阅。

  5. 在“资源组”字段中,请选择一个现有资源组,或选择“新建”并输入新资源组的名称。

  6. 在“存储帐户名称”字段中,输入帐户的名称。 注意以下准则:

    • 该名称在 Azure 中必须唯一。
    • 名称的长度必须介于 3 到 24 个字符之间。
    • 名称只能包含数字和小写字母。
  7. 在“位置”字段中选择存储帐户的位置,或使用默认位置。

  8. 对于其余设置,请配置以下各项:

  9. 在“创建存储帐户”页的“项目详细信息”部分中,选择“订阅”和“资源组”的所需值。

  10. 在“创建存储帐户”页的“实例详细信息”部分中,输入“存储帐户名称”,然后选择“位置”“帐户类型”和“复制”的值。

  11. 选择“查看+创建”可查看存储帐户设置。

  12. 通过验证后,请选择“创建”。

创建 Blob 存储容器

创建 Blob 存储帐户后,将之打开,然后:

  1. 选择“存储资源管理器(预览)”

  2. 然后右键单击“BLOB 容器”

  3. 从下拉列表中选择“创建 blob 容器”

    创建 Blob 容器的屏幕截图。

  4. 在“新容器”表单中输入名称。 稍后将使用此名称作为“blob container name”的值来提供对 Blob 存储帐户的访问。 注意以下准则:

    • 此名称只能包含小写字母、数字和连字符。
    • 此名称必须以字母或数字开头。
    • 每个连字符的前后必须为有效的非连字符字符。
    • 名称的长度必须介于 3 到 63 个字符之间。

添加 Blob 存储配置信息

找到所需的 Blob 存储密钥,以便为机器人配置 Blob 存储,如上所示:

  1. 在 Azure 门户中打开 Blob 存储帐户,然后选择“设置”部分中的“访问密钥”
  2. 若要将机器人配置为访问 Blob 存储帐户,请使用“连接字符串”作为“blob 连接字符串”的值。

在配置文件中添加以下信息。

appsettings.json

"BlobConnectionString": "<your-blob-connection-string>",
"BlobContainerName": "<your-blob-container-name>",

安装 Blob 存储包

如果事先尚未安装以下包,请先予安装。

安装 Microsoft.Bot.Builder.Azure.Blobs NuGet 包。 有关详细信息,请参阅使用 NuGet 包管理器在 Visual Studio 中安装和管理包

Blob 存储实现

“Blob 存储”用于存储机器人状态。

注意

自版本 4.10 起,Microsoft.Bot.Builder.Azure.AzureBlobStorage 已弃用。 其位置中使用新 Microsoft.Bot.Builder.Azure.Blobs.BlobsStorage

以下示例代码运行时,使用的是与上面提供的内存存储示例相同的机器人代码,差异之处于此列出。

以下代码段演示如何为“myStorage”实现 Blob 存储,替换本地内存存储。

首先需要更新 Startup.cs 以引用 Bot Builder Azure blob 库:

Startup.cs

using Microsoft.Bot.Builder.Azure.Blobs;

接下来,在 Startup.cs 中的 ConfigureServices 方法中创建 BlobsStorage 对象,从 appsettings.json 中传入值。 这将通过依赖项注入传递到 EchoBot 构造函数中。

//Use Azure Blob storage, instead of in-memory storage.
services.AddSingleton<IStorage>(
    new BlobsStorage(
        Configuration.GetValue<string>("BlobConnectionString"),
        Configuration.GetValue<string>("BlobContainerName")
        ));

首先需要更新 EchoBot.cs 以引用 Bot Builder Azure blob 库:

EchoBot.cs

using Microsoft.Bot.Builder.Azure.Blobs;

接下来,删除或注释掉创建 MemoryStorage 变量的代码行“private static readonly MemoryStorage _myStorage = new MemoryStorage();”,并创建一个新变量,用于将用户输入保存到 Blob 存储。

EchoBot.cs

// variable used to save user input to CosmosDb Storage.
private readonly IStorage _myStorage;

然后向 EchoBot 构造函数传入 IStorage 对象:

public EchoBot(IStorage storage)
{
    if (storage is null) throw new ArgumentNullException();
    _myStorage = storage;
}

将存储设置为指向 Blob 存储帐户之后,机器人代码可以通过 Blob 存储来存储和检索数据。

将存储设置为指向 Blob 存储帐户之后,机器人代码可以通过 Blob 存储来存储和检索数据。

启动 Blob 存储机器人

在本地运行机器人。

启动 Emulator 并连接 Blob 存储机器人

接下来,启动 Emulator,然后在 Emulator 中连接到机器人:

  1. 选择 Emulator“欢迎使用”选项卡中的“新建机器人配置”链接。
  2. 填写用于连接到机器人的字段,并指定启动机器人时要在网页上显示的信息。

与 Blob 存储机器人交互

向机器人发送消息,机器人会列出所收到的消息。

与机器人的对话,显示机器人保存了用户的消息列表。

查看 Blob 存储数据

运行机器人并保存信息以后,即可在 Azure 门户的“存储资源管理器”选项卡下查看。

Blob 脚本存储

Azure Blob 脚本存储提供专门的存储选项,可以轻松地以记录脚本形式保存和检索用户聊天内容。 Azure Blob 记录存储适合在调试机器人性能时自动捕获要检查的用户输入。

注意

Python 目前不支持 Azure Blob 记录存储。 虽然 JavaScript 支持 Blob 记录存储,但以下说明仅适用于 C#。

设置 Blob 记录存储容器

Azure Blob 脚本存储可以使用通过上面的“创建 Blob 存储帐户”和“添加配置信息”部分详述的步骤创建的 Blob 存储帐户。 现在添加一个容器用于保存脚本

创建要用作脚本存储的 Blob 容器的屏幕截图。

  1. 打开 Azure Blob 存储帐户。
  2. 选择“存储资源管理器”
  3. 右键单击“BLOB 容器”,并选择“创建 Blob 容器”。
  4. 输入记录容器的名称,然后选择“确定”。 (此处输入了 mybottranscripts

Blob 记录存储实现

以下代码将脚本存储指针 _myTranscripts 连接到新的 Azure Blob 脚本存储帐户。 为了使用新的容器名称 <your-blob-transcript-container-name> 创建此链接,其在 Blob 存储中创建一个新容器用于保存记录文件。

“Blob 记录存储”旨在存储机器人记录。

注意

自版本 4.10 起,Microsoft.Bot.Builder.Azure.AzureBlobTranscriptStore 已弃用。 其位置中使用新 Microsoft.Bot.Builder.Azure.Blobs.BlobsTranscriptStore

echoBot.cs

using Microsoft.Bot.Builder.Azure.Blobs;

public class EchoBot : ActivityHandler
{
   ...

   private readonly BlobsTranscriptStore _myTranscripts = new BlobsTranscriptStore("<your-azure-storage-connection-string>", "<your-blob-transcript-container-name>");

   ...
}

在 Azure Blob 记录中存储用户对话

可以使用 Blob 容器来存储记录以后,即可保留用户与机器人的对话内容。 这些聊天内容可以在以后用作调试工具,以便了解用户与机器人的交互情况。 每次在 Emulator 中选择“重启对话”都会启动新记录对话列表的创建。 以下代码将在存储的脚本文件中保留用户聊天输入。

  • 当前脚本是使用 LogActivityAsync 保存的。
  • 可以使用 ListTranscriptsAsync 检索已保存的脚本。 在此示例代码中,存储的每个记录的 ID 将保存到名为“storedTranscripts”的列表。 稍后将使用此列表来管理保留的已存储 Blob 脚本数。

echoBot.cs


protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    await _myTranscripts.LogActivityAsync(turnContext.Activity);

    List<string> storedTranscripts = new List<string>();
    PagedResult<Microsoft.Bot.Builder.TranscriptInfo> pagedResult = null;
    var pageSize = 0;
    do
    {
       pagedResult = await _myTranscripts.ListTranscriptsAsync("emulator", pagedResult?.ContinuationToken);
       pageSize = pagedResult.Items.Count();

       // transcript item contains ChannelId, Created, Id.
       // save the channelIds found by "ListTranscriptsAsync" to a local list.
       foreach (var item in pagedResult.Items)
       {
          storedTranscripts.Add(item.Id);
       }
    } while (pagedResult.ContinuationToken != null);

    ...
}

管理已存储的 Blob 脚本

尽管存储的脚本可用作调试工具,但一段时间后,存储的脚本数可能会大大增加,以致难以照料。 下面提供的附加代码使用 DeleteTranscriptAsync 从 Blob 脚本存储中删除除最后三个检索到的脚本项以外的其他所有脚本。

echoBot.cs


protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    await _myTranscripts.LogActivityAsync(turnContext.Activity);

    List<string> storedTranscripts = new List<string>();
    PagedResult<Microsoft.Bot.Builder.TranscriptInfo> pagedResult = null;
    var pageSize = 0;
    do
    {
       pagedResult = await _myTranscripts.ListTranscriptsAsync("emulator", pagedResult?.ContinuationToken);
       pageSize = pagedResult.Items.Count();

       // transcript item contains ChannelId, Created, Id.
       // save the channelIds found by "ListTranscriptsAsync" to a local list.
       foreach (var item in pagedResult.Items)
       {
          storedTranscripts.Add(item.Id);
       }
    } while (pagedResult.ContinuationToken != null);

    // Manage the size of your transcript storage.
    for (int i = 0; i < pageSize; i++)
    {
       // Remove older stored transcripts, save just the last three.
       if (i < pageSize - 3)
       {
          string thisTranscriptId = storedTranscripts[i];
          try
          {
             await _myTranscripts.DeleteTranscriptAsync("emulator", thisTranscriptId);
           }
           catch (System.Exception ex)
           {
              await turnContext.SendActivityAsync("Debug Out: DeleteTranscriptAsync had a problem!");
              await turnContext.SendActivityAsync("exception: " + ex.Message);
           }
       }
    }
    ...
}

有关该类的详细信息,请参阅 Azure Blob 记录存储

其他信息

使用 eTag 管理并发

在机器人代码示例中,我们将每个 IStoreItemeTag 属性设置为 *。 存储对象的 eTag(实体标记)成员在 Cosmos DB 中用于管理并发。 如果在机器人向存储中写入内容时,该机器人的另一实例更改了同一存储中的对象,eTag 会指示数据库应该如何处理。

最后一次写入才算 - 允许覆盖

星号 (*) 的 eTag 属性值指示最后一次写入才算数。 在创建新的数据存储时,可以将属性的 eTag 设置为 *,以表明以前未保存正在编写的数据,或者想要最后一次写入覆盖任何以前保存的属性。 如果并发性对于机器人来说不是问题,则对于任何要写入的数据,将 eTag 属性设置为 * 就可以允许覆盖。

保持并发并防止覆盖

将数据存储到 Cosmos DB 中时,若要防止对属性进行并发访问并避免覆盖另一个机器人实例的更改,可以对 eTag 使用 * 之外的值。 当机器人尝试保存状态数据但 eTag 的值与存储中 eTag 的值不同时,它会收到包含 etag conflict key= 消息的错误响应。

默认情况下,每当机器人写入到该项时,Cosmos DB 存储都会检查存储对象的 eTag 属性是否等同,然后在每次写入之后将其更新为新的惟一值。 如果写入的 eTag 属性与存储中的 eTag 不匹配,这意味着另一个机器人或线程已更改数据。

例如,假设你想让机器人编辑已保存的注释,但不希望机器人覆盖另一个机器人实例所做的更改。 如果另一个机器人实例已进行了编辑,你希望用户使用最新更新编辑版本。

首先,创建实现 IStoreItem 的类。

EchoBot.cs

public class Note : IStoreItem
{
    public string Name { get; set; }
    public string Contents { get; set; }
    public string ETag { get; set; }
}

接下来,通过创建存储对象创建初始注释,并将该对象添加到存储。

EchoBot.cs

// create a note for the first time, with a non-null, non-* ETag.
var note = new Note { Name = "Shopping List", Contents = "eggs", ETag = "x" };

var changes = Dictionary<string, object>();
{
    changes.Add("Note", note);
};
await NoteStore.WriteAsync(changes, cancellationToken);

然后访问和更新注释,保留从存储中读取的 eTag

EchoBot.cs

var note = NoteStore.ReadAsync<Note>("Note").Result?.FirstOrDefault().Value;

if (note != null)
{
    note.Contents += ", bread";
    var changes = new Dictionary<string, object>();
    {
         changes.Add("Note1", note);
    };
    await NoteStore.WriteAsync(changes, cancellationToken);
}

如果在写入变更之前在存储中更新了注释,调用 Write 将引发异常。

若要保持并发,始终从存储读取属性,然后修改读取的属性,以便保持 eTag。 如果从存储中读取用户数据,响应将包含 eTag 属性。 如果更改数据并将更新后的数据写入到存储中,请求应包含指定与之前读取值相同的值的 eTag 属性。 但是,编写将其 eTag 设置为 * 的对象将允许写入覆盖任何其他更改。

后续步骤

现在你已经了解如何直接从存储中进行读写,接下来让我们来看看如何使用状态管理器来执行这些操作。