在有当前用户的情况下,我(不建议)升级了我的机器人的RootDialog对话数据-特别是一个新的成员变量。
这意味着某些用户的序列化对话状态与当前版本的bot代码不匹配。
如果用户联系机器人,这很好-他们会在机器人上引发异常并重置堆栈的同时收到“机器人出现问题”消息。在那之后一切都会好起来的。
但是,我们希望主动向机器人发送消息,并且由于序列化不匹配(BadRequest
异常),目前所有这些消息都失败了。
如何为IDialogContext之外的给定用户/通道解析IBotData和IDialogStack?我正在Azure数据库上使用SQL bot数据存储(SqlBotDataEntities)。
我想在管理员网站上的控制器方法中执行此操作,然后调用stack.Reset()。这可能吗?
答案 0 :(得分:1)
尝试对 IBotData 进行 LoadAsync 操作会导致堆栈自动重置。 SqlBotDataEntities 表中有足够的信息来创建 Activity ,并将其用于范围。 (由于 SqlBotDataContext 是Azure扩展库的内部组件,因此您需要在代码中重复它。)
类似的东西:
public async Task<HttpResponseMessage> Post()
{
using (var context = new SqlBotDataContext(ConfigurationManager.ConnectionStrings["BotDataContextConnectionString"].ConnectionString))
{
try
{
foreach(var botData in context.BotData)
{
if(botData.BotStoreType == BotStoreType.BotPrivateConversationData)
{
var message = Activity.CreateMessageActivity();
message.ChannelId = botData.ChannelId;
message.Timestamp = botData.Timestamp;
message.From = new ChannelAccount(id: botData.UserId);
message.Conversation = new ConversationAccount(id: botData.ConversationId);
message.Recipient = new ChannelAccount(id: botData.BotId);
message.ServiceUrl = botData.ServiceUrl;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var scopedData = scope.Resolve<IBotData>();
await scopedData.LoadAsync(default(CancellationToken));
//resetting the stack is not necessary, since .LoadAsync will fail silently, and reset it
//var stack = scope.Resolve<IDialogStack>();
//stack.Reset();
await scopedData.FlushAsync(default(CancellationToken));
}
}
}
}
catch (System.Data.SqlClient.SqlException err)
{
throw new HttpException((int)HttpStatusCode.InternalServerError, err.Message);
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
复制以下内容
internal class SqlBotDataContext : System.Data.Entity.DbContext
{
public SqlBotDataContext(string connectionString)
: base(connectionString)
{
System.Data.Entity.Database.SetInitializer<SqlBotDataContext>(null);
}
/// <summary>
/// Throw if the database or SqlBotDataEntities table have not been created.
/// </summary>
static internal void AssertDatabaseReady()
{
//var connectionString = Utils.GetAppSetting(AppSettingKeys.SqlServerConnectionString);
var connectionString = ConfigurationManager.ConnectionStrings["BotDataContextConnectionString"].ConnectionString;
using (var context = new SqlBotDataContext(connectionString))
{
if (!context.Database.Exists())
throw new ArgumentException("The sql database defined in the connection has not been created. See https://github.com/Microsoft/BotBuilder-Azure/tree/master/CSharp");
if (context.Database.SqlQuery<int>(@"IF EXISTS (SELECT * FROM sys.tables WHERE name = 'SqlBotDataEntities')
SELECT 1
ELSE
SELECT 0").SingleOrDefault() != 1)
throw new ArgumentException("The SqlBotDataEntities table has not been created in the database. See https://github.com/Microsoft/BotBuilder-Azure/tree/master/CSharp");
}
}
public DbSet<SqlBotDataEntity> BotData { get; set; }
}
public enum BotStoreType
{
BotConversationData = 0,
BotPrivateConversationData = 1,
BotUserData = 2
}
internal class SqlBotDataEntity : IAddress
{
private static readonly JsonSerializerSettings serializationSettings = new JsonSerializerSettings()
{
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore
};
internal SqlBotDataEntity() { Timestamp = DateTimeOffset.UtcNow; }
internal SqlBotDataEntity(BotStoreType botStoreType, string botId, string channelId, string conversationId, string userId, object data)
{
this.BotStoreType = botStoreType;
this.BotId = botId;
this.ChannelId = channelId;
this.ConversationId = conversationId;
this.UserId = userId;
this.Data = Serialize(data);
Timestamp = DateTimeOffset.UtcNow;
}
#region Fields
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Index("idxStoreChannelUser", 1)]
[Index("idxStoreChannelConversation", 1)]
[Index("idxStoreChannelConversationUser", 1)]
public BotStoreType BotStoreType { get; set; }
public string BotId { get; set; }
[Index("idxStoreChannelConversation", 2)]
[Index("idxStoreChannelUser", 2)]
[Index("idxStoreChannelConversationUser", 2)]
[MaxLength(200)]
public string ChannelId { get; set; }
[Index("idxStoreChannelConversation", 3)]
[Index("idxStoreChannelConversationUser", 3)]
[MaxLength(200)]
public string ConversationId { get; set; }
[Index("idxStoreChannelUser", 3)]
[Index("idxStoreChannelConversationUser", 4)]
[MaxLength(200)]
public string UserId { get; set; }
public byte[] Data { get; set; }
public string ETag { get; set; }
public string ServiceUrl { get; set; }
[Required]
public DateTimeOffset Timestamp { get; set; }
#endregion Fields
#region Methods
private static byte[] Serialize(object data)
{
using (var cmpStream = new MemoryStream())
using (var stream = new GZipStream(cmpStream, CompressionMode.Compress))
using (var streamWriter = new StreamWriter(stream))
{
var serializedJSon = JsonConvert.SerializeObject(data, serializationSettings);
streamWriter.Write(serializedJSon);
streamWriter.Close();
stream.Close();
return cmpStream.ToArray();
}
}
private static object Deserialize(byte[] bytes)
{
using (var stream = new MemoryStream(bytes))
using (var gz = new GZipStream(stream, CompressionMode.Decompress))
using (var streamReader = new StreamReader(gz))
{
return JsonConvert.DeserializeObject(streamReader.ReadToEnd());
}
}
internal ObjectT GetData<ObjectT>()
{
return ((JObject)Deserialize(this.Data)).ToObject<ObjectT>();
}
internal object GetData()
{
return Deserialize(this.Data);
}
internal static async Task<SqlBotDataEntity> GetSqlBotDataEntity(IAddress key, BotStoreType botStoreType, SqlBotDataContext context)
{
SqlBotDataEntity entity = null;
var query = context.BotData.OrderByDescending(d => d.Timestamp);
switch (botStoreType)
{
case BotStoreType.BotConversationData:
entity = await query.FirstOrDefaultAsync(d => d.BotStoreType == botStoreType
&& d.ChannelId == key.ChannelId
&& d.ConversationId == key.ConversationId);
break;
case BotStoreType.BotUserData:
entity = await query.FirstOrDefaultAsync(d => d.BotStoreType == botStoreType
&& d.ChannelId == key.ChannelId
&& d.UserId == key.UserId);
break;
case BotStoreType.BotPrivateConversationData:
entity = await query.FirstOrDefaultAsync(d => d.BotStoreType == botStoreType
&& d.ChannelId == key.ChannelId
&& d.ConversationId == key.ConversationId
&& d.UserId == key.UserId);
break;
default:
throw new ArgumentException("Unsupported bot store type!");
}
return entity;
}
#endregion
}