适用于:SDK v4
可以对存储对象进行直接读写,不需使用中间件或上下文对象。 这可能适用于您的机器人用来保留对话内容的数据,或者来自您的机器人对话流程之外来源的数据。 在此数据存储模型中,直接从存储中读取数据,而不使用状态管理器。 本文中的代码示例演示如何使用内存存储、Cosmos DB、Azure Blob和 Azure Blob 记录对存储进行数据读写。
重要
Bot Framework SDK 和 Bot Framework Emulator 已在 GitHub 上存档。 项目不再更新或维护。 自 2025 年 12 月 31 日起,Bot Framework SDK 的支持票证将不再提供服务。
若要使用所选的 AI 服务、业务流程和知识生成代理,请考虑使用 Microsoft 365 代理 SDK。 代理 SDK 对 C#、JavaScript 或 Python 具有语言支持。 可以在 aka.ms/agents 了解有关代理 SDK 的详细信息。 如果现有的机器人是使用 Bot Framework SDK 生成的,则可以将机器人更新到代理 SDK。 查看 Bot Framework SDK 到代理 SDK 迁移指南的核心更改和更新。
如果要构建设计为在 Microsoft Teams 中工作的协作代理,请考虑使用 Teams SDK。 它为在 Teams 环境中运行的代理提供 Teams 特定的 API、自适应卡支持和内置 AI 协同调度功能。 可以在 Teams SDK(Teams AI 库)中了解详细信息。
如果要查找基于 SaaS 的代理平台,请考虑 Microsoft Copilot Studio。
先决条件
- 如果没有 Azure 订阅,请在开始前创建一个试用版订阅帐户。
- 熟悉在本地创建机器人
- 用于 Visual Studio (C#)、Node.js 或 Yeoman 的 Bot Framework SDK v4 模板。
注意
可以在 Visual Studio 中安装模板。
- 在菜单中,选择扩展,然后选择管理扩展。
- 在管理扩展对话框中,搜索并安装 Bot Framework v4 SDK templates for Visual Studio。
有关将 .NET 机器人部署到 Azure 的信息,请参阅如何预配和发布机器人。
关于此示例
本文中的示例代码首先演示基本聊天机器人的结构,然后通过添加更多的代码(下面会提供)来扩展该机器人的功能。 此扩展代码会创建一个列表,用于在收到用户输入时保留这些输入。 在每一轮中,保存到内存的用户输入完整列表都会回显给用户。 然后会将包含此输入列表的数据结构进行修改,以保存到存储中。 随着向此示例代码添加更多功能,将探索各种类型的存储。
重要
本文包含使用配置文件中的连接字符串进行内部存储连接的旧代码示例。 Azure 建议使用最安全的可用身份验证流。 如果要连接到 Azure 资源,建议使用 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 中连接到机器人:
在 Emulator 的欢迎选项卡中,选择新建机器人配置链接。
根据启动机器人时显示的网页中的信息,填写相关字段以连接到你的机器人。
与机器人交互
向机器人发送消息 机器人会列出它所收到的消息。
本文的其余部分将演示如何保存到持久存储而不是机器人的内部内存。
使用 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 数据库和应用创建的深入说明,请参阅适用于 .NET、Node.js 或 Python 的快速入门。
创建数据库帐户
转到 Azure 门户以创建 Azure Cosmos DB 帐户。 搜索并选择“Azure Cosmos DB”。
在 Azure Cosmos DB 页中,选择“新建”以显示“创建 Azure Cosmos DB 帐户”页。
提供以下字段的值:
- 订阅。 选择要用于此 Azure Cosmos 帐户的 Azure 订阅。
- 资源组。 选择现有资源组,或者选择“新建”并输入新资源组的名称。
-
帐户名。 输入标识此 Azure Cosmos 帐户的名称。 由于 documents.azure.cn 会附加到你提供的名称后面,用于创建 URI,因此请使用唯一的名称。 注意以下准则:
- 该名称在 Azure 中必须唯一。
- 名称的长度必须介于 3 到 31 个字符之间。
- 名称只能包含小写字母、数字和连字符 (-)。
- API。 选择 Core(SQL)
- Location。 选择离用户最近的位置,使他们能够以最快的速度访问数据。
选择“查看 + 创建” 。
通过验证后,请选择“创建”。
创建帐户需要几分钟时间。 等待门户中显示“祝贺你! 已创建 Azure Cosmos DB 帐户”页。
添加数据库
注意
不要自行创建容器。 机器人将在创建内部 Cosmos DB 客户端时为你创建该容器,并确保其配置正确,以便用于存储机器人状态。
在新建的 Cosmos DB 帐户中导航到“数据资源管理器”页,然后从“新建容器”下拉列表中选择“新建数据库”。 随后,在窗口的右侧会打开一个面板,你可以在其中输入新数据库的详细信息。
输入新数据库的 ID,并根据需要设置吞吐量(以后可以更改),最后选择“确定”以创建数据库。 请记下此数据库 ID,以便稍后在配置机器人时使用。
创建 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;
然后向 IStorage 构造函数传入 EchoBot 对象:
public EchoBot(IStorage storage)
{
if (storage is null) throw new ArgumentNullException();
_myStorage = storage;
}
启动 Cosmos DB 机器人
在本地运行您的机器人程序。
使用 Bot Framework Emulator 测试 Cosmos DB 机器人
现在启动 Bot Framework Emulator 并连接到机器人:
选择 Emulator 欢迎 选项卡中的 创建新的机器人配置 链接。
根据启动机器人时显示的网页中的信息,填写相关字段以连接到你的机器人。
与 Cosmos DB 机器人交互
向机器人发送消息,机器人会列出所收到的消息。
查看 Cosmos DB 数据
运行机器人并保存信息以后,即可在 Azure 门户的“数据资源管理器”选项卡下查看存储的数据。
使用 Blob 存储
Azure Blob 存储是 Azure 的适用于云的对象存储解决方案。 Blob 存储最适合存储巨量的非结构化数据,例如文本或二进制数据。 本部分介绍如何创建 Azure Blob 存储帐户和容器,以及如何从机器人引用 Blob 存储容器。
有关 Blob 存储的更多信息,请参阅什么是 Azure Blob 存储?
创建 Blob 存储帐户
若要在机器人中使用 Blob 存储,需在编写代码之前进行一些设置。
在 Azure 门户中,选择“所有服务”。
在“所有服务”页的“特别推荐”部分,选择“存储帐户”。
在“存储帐户”页上,选择“新建”。
在“订阅”字段中,选择要在其中创建存储帐户的订阅。
在“资源组”字段中,请选择一个现有资源组,或选择“新建”并输入新资源组的名称。
在“存储帐户名称”字段中,输入帐户的名称。 注意以下准则:
- 该名称在 Azure 中必须唯一。
- 名称的长度必须介于 3 到 24 个字符之间。
- 名称只能包含数字和小写字母。
在“位置”字段中选择存储帐户的位置,或使用默认位置。
对于其余设置,请配置以下各项:
- 性能:标准。 了解有关性能的更多信息。
- 帐户种类:BlobStorage。 了解有关存储帐户的更多信息。
- 复制:保留默认设置。 了解有关冗余的更多信息。
在“创建存储帐户”页的“项目详细信息”部分中,选择“订阅”和“资源组”的所需值。
在“创建存储帐户”页的“实例详细信息”部分中,输入“存储帐户名称”,然后选择“位置”、“帐户类型”和“复制”的值。
选择“查看+创建”可查看存储帐户设置。
通过验证后,请选择“创建”。
创建 Blob 存储容器
创建 Blob 存储帐户后,将之打开,然后:
选择“存储资源管理器(预览)”。
然后右键单击 BLOB 容器
从下拉列表中选择“创建 blob 容器”。
在“新容器”表单中输入名称。 您将使用此名称作为“blob container name”的值,以访问您的 Blob 存储账户。 注意以下准则:
- 此名称只能包含小写字母、数字和连字符。
- 此名称必须以字母或数字开头。
- 每个连字符前后都必须是有效的非连字符。
- 名称的长度必须介于 3 到 63 个字符之间。
添加 Blob 存储配置信息
找到所需的 Blob 存储密钥,以便为机器人配置 Blob 存储,如上所示:
在 Azure 门户中打开 Blob 存储帐户,然后选择“设置”部分中的“访问密钥”。
若要将机器人配置为可访问您的 Blob 存储帐户,请使用 连接字符串 作为 blob 连接字符串 的值。
在配置文件中添加以下信息。
appsettings.json
"BlobConnectionString": "<your-blob-connection-string>",
"BlobContainerName": "<your-blob-container-name>",
安装 Blob 存储包
如果事先尚未安装以下包,请先予安装。
安装 Microsoft.Bot.Builder.Azure.Blobs NuGet 包。 有关使用 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;
然后向 IStorage 构造函数传入 EchoBot 对象:
public EchoBot(IStorage storage)
{
if (storage is null) throw new ArgumentNullException();
_myStorage = storage;
}
将存储配置为指向 Blob 存储帐户后,机器人代码现在将从 Blob 存储中存储和检索数据。
将存储配置为指向 Blob 存储帐户后,机器人代码就会在 Blob 存储中存储和检索数据。
启动你的 Blob 存储机器人
在本地运行您的机器人程序。
启动模拟器并连接你的 Blob 存储机器人
接下来,启动 Emulator,然后在 Emulator 中连接到机器人:
在 Emulator 的“欢迎”选项卡中,选择创建新的机器人配置链接。
根据启动机器人时显示的网页中的信息,填写相关字段以连接到你的机器人。
与 Blob 存储机器人交互
向机器人发送消息,机器人会列出所收到的消息。
查看 Blob 存储数据
运行机器人并保存信息以后,即可在 Azure 门户的“存储资源管理器”选项卡下查看。
Blob 转录文本存储
Azure Blob 脚本存储提供专门的存储选项,可以轻松地以记录脚本形式保存和检索用户聊天内容。 Azure Blob 记录存储适合在调试机器人性能时自动捕获要检查的用户输入。
注意
Python 目前不支持 Azure Blob 记录存储。 虽然 JavaScript 支持 Blob 记录存储,但以下说明仅适用于 C#。
设置 Blob 听录文本存储容器
Azure Blob 转录文本存储可以使用按照上文“创建 Blob 存储帐户”和“添加配置信息”部分中详述的步骤创建的同一 Blob 存储帐户。 我们现在添加一个容器来存放转录文本
打开 Azure Blob 存储帐户。
选择“存储资源管理器”。
右键单击“BLOB 容器”,并选择“创建 Blob 容器”。
输入记录容器的名称,然后选择“确定”。 (我们输入了 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 管理并发
在机器人代码示例中,我们将每个 eTag 的 IStoreItem 属性设置为 *。 存储对象的 eTag(实体标记)成员在 Cosmos DB 中用于管理并发。 如果在机器人向存储中写入内容时,该机器人的另一实例更改了同一存储中的对象,eTag 会指示数据库应该如何处理。
以最后一次写入为准 - 允许覆盖
值为星号 (*) 的 eTag 属性表示以最后一个写入者为准。 在创建新的数据存储时,可以将属性的 eTag 设置为 *,以表明以前未保存正在编写的数据,或者想要最后一次写入覆盖任何以前保存的属性。 如果并发性对于机器人来说不是问题,则对于任何要写入的数据,将 eTag 属性设置为 * 就可以允许覆盖。
保持并发性并防止覆盖
将数据存储到 Cosmos DB 时,如果要防止对某个属性的并发访问,并避免覆盖来自机器人另一个实例的更改,请为 eTag 使用不同于 * 的值。 当机器人尝试保存状态数据但 etag conflict key= 的值与存储中 eTag 的值不同时,它会收到包含 eTag 消息的错误响应。
默认情况下,每当机器人写入到该项时,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 设置为 * 的对象将允许写入覆盖任何其他更改。
后续步骤
现在你已经了解如何直接从存储中进行读写,接下来让我们来看看如何使用状态管理器来执行这些操作。