问题
One of the functionalities of my bot is handling a Shopping Cart. The user can add items anywhere in the conversation and then finish shopping to close the product cart.
To avoid passing the cart from dialog to dialog I would like to create a UserProfile
property in the UserState
(The UserProfile
property has a ShoppingCart
attribute) but I don't quite know how to use this properly.
My Main Dialog contains a set of Child Dialogs and some of them need to be able to access the ShoppingCart
object. I found some examples in the samples but none of them do what I want to achieve. In the State Management sample:
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// Get the state properties from the turn context.
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());
if (string.IsNullOrEmpty(userProfile.Name))
{
// First time around this is set to false, so we will prompt user for name.
if (conversationData.PromptedUserForName)
{
// Set the name to what the user provided.
userProfile.Name = turnContext.Activity.Text?.Trim();
// Acknowledge that we got their name.
await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");
// Reset the flag to allow the bot to go though the cycle again.
conversationData.PromptedUserForName = false;
}
else
{
// Prompt the user for their name.
await turnContext.SendActivityAsync($"What is your name?");
// Set the flag to true, so we don't prompt in the next turn.
conversationData.PromptedUserForName = true;
}
}
If I understand correctly, every time he wants to get the accessor a new Property is created? Or once a property is created if you call CreateProperty
no property will be creater and the accessor is returned?
I thought about getting the accessor on the Bot and then passing it to MainDialog
and then to the ChildDialogs
but it kinda defeats the purpose of not passing the ShoppingCart
through Dialogs.
Can't I get the accessors without having to create a Property every time?
I've read this issue which gives a solution to my problem, but then I saw @johnataylor's comment saying
The pattern we follow is to defer the creation of the accessor until we need it - this seems to hide the inherent noise the most effectively.
When should I create the accessors if I want to get the ShoppingCart
(which is inside the UserProfile
property that I need to access) inside my Dialogs?
回答1:
Fast Answer: You should create the accessor in the all the dialogs where you need to manipulate the state.
Detailed Answer:
CreateProperty does not physically create the property, it just:
Creates a property definition and register it with this BotState
CreateProperty() will return you a BotStatePropertyAccessor from which you can call GetAsync,SetAsync and DeleteAsync those will Get, Set and delete a property from the state cache in the turn context.(Internal cached bot state)
When you call BotState.SaveChangesAsync() this will:
If it has changed, writes to storage the state object that is cached in the current context object for this turn.
Each call of GetAsync,SetAsync will actually call BotState.LoadAsync() first to:
Reads in the current state object and caches it in the context object for this turn.
And when GetAsync() is called and no key is found, it will automatically call SetAsync to set that new property
If you are using AutoSaveStateMiddleware that middleware will:
automatically call .SaveChanges() at the end of the turn for all BotState class it is managing.
来源:https://stackoverflow.com/questions/58338918/how-to-use-state-accessors-to-get-properties-in-bot-framework