Получение данных беседы чат-бота в Azure

Сначала краткая предыстория: платформа Bot хранит данные беседы в хранилище либо в таблицах Azure, либо в Cosmos DB (в моем случае - в таблицах Azure). Для каждого разговора в таблицу Azure вносится запись с меткой времени, идентификатором пользователя, сообщениями разговора и другими деталями.

Я пытаюсь получить сведения о разговоре из хранилища таблиц Azure, используя собственный код на C #, как показано ниже.

Microsoft.WindowsAzure.Storage.CloudStorageAccount storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(connectionString);
Microsoft.WindowsAzure.Storage.Table.CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("botdata");
TableQuery<DynamicTableEntity> projectionQuery = new TableQuery<DynamicTableEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "directline:user"));
var dataRow = table.ExecuteQuery(projectionQuery).Where(q => q.RowKey == "souvik").FirstOrDefault();
var conversation = Encoding.UTF8.GetString(dataRow.Properties["Data"].BinaryValue);
Console.WriteLine(conversation);

Я получаю какой-то бредовый текст, что-то вроде этого - \u001f�\b\0\0\0\0\0\u0004\0��\u0005\0C���\u0002\0\0\0, когда я извлекаю данные, хранящиеся в двоичном формате в хранилище таблиц Azure. Данные выглядят примерно так, как показано ниже в обозревателе хранилища Azure:

введите описание изображения здесь

Я пробовал и тестировал для каждого разговора, и я заметил, что каждый раз, когда я набираю и отправляю данные боту и получаю ответ, новые записи добавляются в таблицу Azure с правильной меткой времени. Это правильный способ получения данных или я что-то делаю не так? Дайте мне знать, если я могу добавить больше деталей.

Обновление-1:

Я сослался на это - Как получить сохраненные данные разговора в Azure ( Tablelogger), но не получил особой помощи в моей проблеме.

Обновление-2

Основываясь на ответе Фей ниже, я просто заметил, что когда я использую PartitionKey как directline:user или directline:conversation, я получаю десериализованные значения как {}, но если я использую directline:private, я получаю что-то вроде этого:

{"ResumptionContext":{"locale":"en-US","isTrustedServiceUrl":true}, "DialogState":"H4sIAAAAAAAEAO1a328iVRRmYGYKbTfbdZtNzLq7GM26DWS2FGi7MVUptUrcVi1VYxrSvcCFjh1mmrl3quyjb74YEx+N/hW+uzFxE/8KX9fsH6HnzAxQ7A8GhHpZoemBDs O953znO+d+99KQFAqF/oIHPuNjNgzm8y29YlvMqnFt3YJfRzeq1E7GP6U20y1zLa2llrQlLZOM5x2DOzZdM6nDbWIk4x85ZUOvfECbu9YhhTtT5Vp6NbtMqunlDE1nFZznF+nM8bWCyaltEoNpm 3oZpvKeHqX29s6+f0MnhlVnJz7nXdkl7DAZH1kMpVIEglAYJ5VDtWaTBmXKl0TnLCLLT8LFJuO0oeUtw6AVDjMx7T1qUluvaA91xs+P5lT0hU0cW8DwRzo4CYhOESAlhv6YIMaJIrdsWjAhJ2aF rjd3m0e06Ni2VSecJj4sfwGp2KE1alN4Gxl+ObPgbwzJEpPRKGBk/HPCkmFUoLqvA4ZM2Wf6YxrdP/aGBaR/DdpfhMVur4TUiUZjqs8i5I8iXwpxsUPLeEfkjs/ST0ydN7sGfN/CWV16T4GRFN /FWBTMFDodkvxL8pOxz4YbZwzNNJgrYRlj7gUOMlTeIJzE3DsQ0NkcY7RRNprbEKcUkqLqDNz0NPBqiPBskgrksikgSphsdXak6kFy6YXzXAHzW1DgPoPFWWzcYoibEutLGwlcLHKD2Icyai JZvpyV9odwH1x4tCQaask9v5t4oSXjDVaxbEMvd0bPaIv403vw8soKyVayy6kH6QxdXH3gpcTtYVfRzGFzxkbmdrRraF7CS1dbPfw6cnFuAmpvUGWbMi6blFZlOTBYO/CZ/yNYiYD4bAOcbcb OIxefw/60n0+j9Jg6JoZD9/dD0bAvRhSk+tNxp3T32HnLNCkubFphizJG6jQHu4lj0Bv/9KJ95xC7/An2B8ZVVPYLhOtAhXIDWf4nFMpz/zBHwqZ+rf3Ws5NvXW/JqJdDfeyWth3DcMtCPNHR 0VDz/axbopJxlK3Y3Zdw0FMqJ3adcrVB+YFVjcg9t3s/nUOUi3BK7B445iHg7HhiddSLzH1/AhCH/sGKtgVbLmoXzJp13hb2JppX0NxCCt3oZ50QlUKC9LPB2fb7ENgmCAgDE/M2mjto4ti2b 7ba9quTJndxK+isB1jWojcuXJNV251K/qaHr55HuRr3vhi5lG3ja1jEtwYgMZJVxsOurpOvWN4gjLmvinrdJOjWdPvV0rQ3JG755/wz8Zxdh6BNziR8hCLRK74jeNNeSX0d50GeuzlX745BztU 3wMmvI604DmxKqrpZ19AP5lrwIOjBqa/K+o+4VIojbPcCn9D6X8gNMNM5zWSQBnSGYwVAEfzqyhhMeQrePJ4nGYbLUTc1C+o9SMQz5eJE9JsJwZrtaL+LGYxCYwLRcPk50pYSiO54gj4t3W4piY WJ1h2azOuIDpRsL5B+PalPfu5PnwgSQVvKJFBHo5hGOaMmu3WD9mKlTb0P8fw4kRjDbeFBkxtQfSxCjr5XJ+pjoj4uQX0I0piCCxUFt54CbYkHP876tkcQ+QOim4mHlnXkXRH2sDSFZglNGsxsBs zax2YOYur4lQrql+kYhoJr8njKisHpcLfbryJpHBlU83DUdiyLe4FnBs1SFs0ymhVUPKmW3l8Vq546khkZJVqNoO6dzkE3rdAqNbncR9LCLfyldEtsvglmxseWNc2KpznXBIxbfQuc+k66QJTET8 RxztrbXrm8F1B5nH7Fe65zOfyvIVI26KnTnAX1bXDrj//erZFi72/Qs62CfWdsm2OntrEOAtYO3BnC+pFWWmWzDmbe93wHKlE/plU3wWr+Xy896kZwz9R33UxcxL6zvBwdDQNvRBbUTdxkhMVxXRSKesU28zeNVmRMBTMAAA=="}

У меня такое чувство, что данные DialogState в приведенном выше JSON нужно расшифровать?

Для каждого сообщения, которое я набираю, и для каждого получаемого ответа в таблицу Azure вставляются три записи, каждая с разными PartitionKey: directline:private, directline:user и directline:conversation.


person Souvik Ghosh    schedule 17.04.2018    source источник
comment
На этом сайте не одобряются удаление вопроса из-за большого количества голосов против, а затем немедленный повторный запрос.   -  person Frauke    schedule 17.04.2018
comment
@FraukeNonnenmacher Я знаю, я получил только голоса против, и никто не сказал, что я сделал не так. Смотрите этот вопрос, он тот же, что я удалил. Вы видите что-то не так в соответствии с рекомендациями по Stackoverflow?   -  person Souvik Ghosh    schedule 17.04.2018
comment
Вы не приводите минимальный, полный и проверяемый пример, и вы даже не даете ожидаемого результата (несмотря на то, что вас просили об этом)   -  person Frauke    schedule 17.04.2018
comment
@FraukeNonnenmacher Я отредактировал и обновил вопрос. Сделал бы это, если бы кто-то сначала указал. Спасибо   -  person Souvik Ghosh    schedule 17.04.2018
comment
Хммм - если я правильно помню, я сначала указал на это.   -  person Frauke    schedule 17.04.2018
comment
Возможный дубликат Как получить сохраненные данные разговора в Azure (Tablelogger)   -  person D4RKCIDE    schedule 17.04.2018


Ответы (3)


Двоичные данные в Azure Table Storage хранятся как Base64 encoded string. Что вам нужно сделать, так это сначала преобразовать эту строку в байты, а затем получить строку из этих байтов.

Что-то вроде:

var conversation = Encoding.UTF8.GetString(Convert.FromBase64String(dataRow.Properties["Data"].BinaryValue));
person Gaurav Mantri    schedule 17.04.2018
comment
Прежде всего Convert.FromBase64String будет принимать только значение string. Здесь я получаю значение в виде массива Byte, который хранится в BinaryValue. Я тоже пробовал это- byte[] data = Convert.FromBase64String("H4sIAAAAAAAEAKuuBQBDv6ajAgAAAA=="); string decodedString = Encoding.UTF8.GetString(data); но это тоже не сработало - person Souvik Ghosh; 17.04.2018
comment
Моя плохая .... Я не запускал код. Прости за это! Позвольте мне сохранить некоторые двоичные данные в таблице и получить их. - person Gaurav Mantri; 17.04.2018
comment
Итак, я попробовал свой код, приведенный выше, и смог правильно вернуть строку. Вы правы, что EntityProperty.BinaryValue возвращает массив байтов. Я попытался преобразовать байтовый массив в строку, и это сработало. Я также скопировал строку в кодировке base64 из обозревателя хранилища и запустил приведенный выше код, и это тоже сработало. Не могли бы вы отредактировать свой вопрос и указать, как данные вставляются в таблицы Azure? Это может дать понять, почему десериализация не работает в вашем случае. - person Gaurav Mantri; 17.04.2018
comment
Это сложная часть. Я создал бота для веб-приложений в Azure, который использует внутреннюю структуру ботов. Глядя на код, я не мог найти никаких следов, иначе я мог бы найти проблему. Вы знаете, есть ли способ это проверить? - person Souvik Ghosh; 17.04.2018
comment
По метаданным это выглядит как свойство JSON - [JsonProperty(PropertyName = "data")] public object Data { get; set; } - person Souvik Ghosh; 17.04.2018
comment
В моем случае это был простой HTML-файл, сохраненный как двоичный контент. Я думаю, что в вашем случае контент является двоичным, и поэтому вы получаете искаженный текст. - person Gaurav Mantri; 17.04.2018

Если вы хотите использовать клиентскую библиотеку WindowsAzure.Storage для получения сущностей из табличного хранилища и извлечения данных из свойства Data, вы можете обратиться к следующему коду.

CloudStorageAccount storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName={your_account_name};AccountKey={your_account_key};EndpointSuffix=core.windows.net");

CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

CloudTable table = tableClient.GetTableReference("botdata");

TableQuery<MessageEntity> query = new TableQuery<MessageEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "emulator:user"));

foreach (MessageEntity entity in table.ExecuteQuery(query))
{
    string mydata = "";
    using (var msi = new MemoryStream(entity.Data))
    using (var mso = new MemoryStream())
    {
        using (var gs = new GZipStream(msi, CompressionMode.Decompress))
        {
            gs.CopyTo(mso);
        }
        mydata = Encoding.UTF8.GetString(mso.ToArray());
    }

    object data = JsonConvert.DeserializeObject(mydata);

    //.....
}

MessageEntity:

public class MessageEntity : TableEntity
{
    public MessageEntity(string pk, string rk)
    {
        this.PartitionKey = pk;
        this.RowKey = rk;
    }

    public MessageEntity() { }

    public string BotId { get; set; }
    public string ChannelId { get; set; }
    public string ConversationId { get; set; }
    public byte[] Data { get; set; }
    public string UserId { get; set; }
}

Результат испытаний:

введите описание изображения здесь

Примечание.

  • В исходном коде code, вы можете найти свойство Data, определенное как byte[] в классе BotDataEntity.
  • В ответе Джейсона Соуэрса он поделился информацией о сериализации и десериализации свойства Data.
person Fei Han    schedule 18.04.2018
comment
Спасибо, Фэй. Я обновил вопрос, указав более подробную информацию в разделе Обновление-2. Не могли бы вы взглянуть? - person Souvik Ghosh; 18.04.2018

Я предполагаю, что вы используете пакет botbuilder-azure

не делайте этого в диалоговом окне, потому что вы можете просто получить доступ к тем же данным в объекте context. просто так случилось, что я написал код

На самом деле это так просто:

private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
    var activity = await result as Activity;

    IBotDataStore<BotData> table = new TableBotDataStore(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);

//use the type of state data you need
    var userData = await table.LoadAsync(Address.FromActivity(activity), BotStoreType.BotUserData, CancellationToken.None );
    var privateConvoData = await table.LoadAsync(Address.FromActivity(activity), BotStoreType.BotPrivateConversationData, CancellationToken.None );
    var convoData = await table.LoadAsync(Address.FromActivity(activity), BotStoreType.BotConversationData, CancellationToken.None);

//in this case I am just replying with the data, but do what you need with it here
    var reply = activity.CreateReply(userData.Data.ToString());
    var reply2 = activity.CreateReply(privateConvoData.Data.ToString());
    var reply3 = activity.CreateReply(convoData.Data.ToString());

    await context.PostAsync(reply);
    await context.PostAsync(reply2);
    await context.PostAsync(reply3);

    context.Wait(MessageReceivedAsync);
}

Если вы заметили в источнике для botbuilder-azure есть сериализовать и десериализовать метод. Где это происходит:

private 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();
    }
}

Итак, нужные вам данные сжаты до той «тарабарщины», которую вы видели. При доступе к данным через метод LoadAsync они также распаковываются, как здесь:

private 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());
    }
}

deserialize вызывается внутри метода LoadAsync в оператор return return new BotData(entity.ETag, entity.GetData()); Это метод GetData(), как показано ниже:

internal ObjectT GetData<ObjectT>()
{
    return ((JObject)Deserialize(this.Data)).ToObject<ObjectT>();
}
person D4RKCIDE    schedule 17.04.2018
comment
Спасибо Джейсону за подробности. Я использовал API Microsoft QnA Maker в качестве службы и создал бот веб-приложения в Azure без специального кода. Я предполагаю, что код, который вы подробно описали, ушел под капот, абстрагированный, когда я создавал бота. Теперь данные, которые регистрируются в таблице, нужны мне в другом приложении. Итак, как мне получить данные (разговор), которые уже сохраняются? - person Souvik Ghosh; 18.04.2018
comment
Кроме того, я попытался распаковать байты, как вы упомянули в приведенном выше коде. Я не получаю данных при выполнении streamReader.ReadToEnd(). - person Souvik Ghosh; 18.04.2018
comment
Чтобы использовать этот метод, ваш другой проект должен содержать botbuilder-azure пакет NuGet, и вам нужно будет каким-то образом передать объект Activity в другое приложение. - person D4RKCIDE; 18.04.2018