Как получить несколько типов объектов, используя один запрос в Azure Table Storage?

101
10

Я пытаюсь понять, как работает хранилище таблиц Azure для создания фидов в стиле facebook, и я зациклился на том, как получить записи.


(Мои вопросы почти такие же, как https://stackoverflow.com/questions/6843689/retrieve-multiple-type-of-entities-from-azure-table-storage, но ссылка в ответе нарушена.)


Это мой предполагаемый подход:


    Создайте личный канал для всех пользователей моего приложения, которые могут содержать разные типы записей (уведомление, обновление статуса и т.д.). Моя идея состоит в том, чтобы хранить их в таблице Azure, сгруппированной по ключу раздела для каждого пользователя.


    Извлеките все записи в одном ключе и передайте их различным представлениям в зависимости от типа записи.


Как запросить хранилище таблиц для всех типов одного и того же базового типа сохраняя при этом свои уникальные свойства?



Для CloudTableQuery<TElement> требуется типизированный объект, если я укажу EntryBase как общий аргумент, я не получаю свойства, специфичные для записи (NotificationSpecificProperty, StatusUpdateSpecificProperty) и наоборот.


Мои объекты:


public class EntryBase : TableServiceEntity
{
public EntryBase()
{

}
public EntryBase(string partitionKey, string rowKey)
{
this.PartitionKey = partitionKey;
this.RowKey = rowKey;
}
}

public class NotificationEntry : EntryBase
{
public string NotificationSpecificProperty { get; set; }
}

public class StatusUpdateEntry : EntryBase
{
public string StatusUpdateSpecificProperty { get; set; }
}


Мой запрос для фида:


List<AbstractFeedEntry> entries = // how do I fetch all entries?

foreach (var item in entries)
{

if(item.GetType() == typeof(NotificationEntry)){

// handle notification

}else if(item.GetType() == typeof(StatusUpdateEntry)){

// handle status update

}

}

спросил(а) 2012-05-22T10:38:00+04:00 8 лет, 6 месяцев назад
1
Решение
101

Наконец-то официальный путь!:)


Посмотрите на образец NoSQL, который делает именно это в этой ссылке из блога группы Azure Storage Team:

Клиентская библиотека Windows Azure Storage 2.0 Таблицы Deep Dive

ответил(а) 2012-11-13T15:48:00+04:00 8 лет назад
72

Есть несколько способов сделать это, и как вы это делаете, это немного зависит от ваших личных предпочтений, а также от потенциальных целей производительности.


    Создайте объединенный класс, который представляет все запрошенные типы. Если бы у меня были StatusUpdateEntry и NotificationEntry, я бы просто объединил каждое свойство в один класс. Сериализатор будет
    автоматически заполнить правильные свойства и оставить остальные нулевыми (или по умолчанию). Если вы также поместили свойство "type" на объект (рассчитанный или установленный в хранилище), вы можете легко включить этот тип. Так как я всегда рекомендую отображать из объекта таблицы свой собственный тип в приложении, это также прекрасно работает (класс используется только для DTO).

Пример:


[DataServiceKey("PartitionKey", "RowKey")]
public class NoticeStatusUpdateEntry
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string NoticeProperty { get; set; }
public string StatusUpdateProperty { get; set; }
public string Type
{
get
{
return String.IsNullOrEmpty(this.StatusUpdateProperty) ? "Notice" : "StatusUpate";
}
}
}

    Отменить процесс сериализации. Вы можете сделать это сами, подключив событие ReadingEntity. Он дает вам необработанный XML, и вы можете выбрать сериализацию, как хотите. Jai Haridas и Pablo Castro дали примерный код для чтения объекта, когда вы не знаете тип (см. Ниже), и вы можете адаптировать его для чтения определенных типов, о которых вы знаете.

Недостатком обоих подходов является то, что вы в конечном итоге извлекаете больше данных, чем вам нужно в некоторых случаях. Вам нужно взвесить это на сколько вы действительно хотите запросить один тип по сравнению с другим. Имейте в виду, что теперь вы можете использовать проекцию в хранилище таблиц, что также уменьшает размер формата проводов и может реально ускорить работу, когда у вас есть более крупные объекты или многие, чтобы вернуться. Если вам когда-либо приходилось запрашивать только один тип, я бы, вероятно, использовал часть RowKey или PartitionKey, чтобы указать тип, который затем позволит мне запрашивать только один тип за раз (вы могли бы использовать свойство, но это не так эффективно для запросов, как PK или RK).


Edit: Как отметил Lucifure, еще один отличный вариант - создать вокруг него. Используйте несколько таблиц, запрос в параллель и т.д. Конечно, вам нужно обменять это со сложностью тайм-аутов и обработки ошибок, но это жизнеспособный и часто хороший вариант, в зависимости от ваших потребностей.


Чтение общего объекта:


[DataServiceKey("PartitionKey", "RowKey")]   
public class GenericEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }

Dictionary<string, object> properties = new Dictionary<string, object>();

internal object this[string key]
{
get
{
return this.properties[key];
}

set
{
this.properties[key] = value;
}
}

public override string ToString()
{
// TODO: append each property
return "";
}
}

void TestGenericTable()
{
var ctx = CustomerDataContext.GetDataServiceContext();
ctx.IgnoreMissingProperties = true;
ctx.ReadingEntity += new EventHandler<ReadingWritingEntityEventArgs>(OnReadingEntity);
var customers = from o in ctx.CreateQuery<GenericTable>(CustomerDataContext.CustomersTableName) select o;

Console.WriteLine("Rows from '{0}'", CustomerDataContext.CustomersTableName);
foreach (GenericEntity entity in customers)
{
Console.WriteLine(entity.ToString());
}
}

// Credit goes to Pablo from ADO.NET Data Service team
public void OnReadingEntity(object sender, ReadingWritingEntityEventArgs args)
{
// TODO: Make these statics
XNamespace AtomNamespace = "http://www.w3.org/2005/Atom";
XNamespace AstoriaDataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices";
XNamespace AstoriaMetadataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

GenericEntity entity = args.Entity as GenericEntity;
if (entity == null)
{
return;
}

// read each property, type and value in the payload
var properties = args.Entity.GetType().GetProperties();
var q = from p in args.Data.Element(AtomNamespace + "content")
.Element(AstoriaMetadataNamespace + "properties")
.Elements()
where properties.All(pp => pp.Name != p.Name.LocalName)
select new
{
Name = p.Name.LocalName,
IsNull = string.Equals("true", p.Attribute(AstoriaMetadataNamespace + "null") == null ? null : p.Attribute(AstoriaMetadataNamespace + "null").Value, StringComparison.OrdinalIgnoreCase),
TypeName = p.Attribute(AstoriaMetadataNamespace + "type") == null ? null : p.Attribute(AstoriaMetadataNamespace + "type").Value,
p.Value
};

foreach (var dp in q)
{
entity[dp.Name] = GetTypedEdmValue(dp.TypeName, dp.Value, dp.IsNull);
}
}

private static object GetTypedEdmValue(string type, string value, bool isnull)
{
if (isnull) return null;

if (string.IsNullOrEmpty(type)) return value;

switch (type)
{
case "Edm.String": return value;
case "Edm.Byte": return Convert.ChangeType(value, typeof(byte));
case "Edm.SByte": return Convert.ChangeType(value, typeof(sbyte));
case "Edm.Int16": return Convert.ChangeType(value, typeof(short));
case "Edm.Int32": return Convert.ChangeType(value, typeof(int));
case "Edm.Int64": return Convert.ChangeType(value, typeof(long));
case "Edm.Double": return Convert.ChangeType(value, typeof(double));
case "Edm.Single": return Convert.ChangeType(value, typeof(float));
case "Edm.Boolean": return Convert.ChangeType(value, typeof(bool));
case "Edm.Decimal": return Convert.ChangeType(value, typeof(decimal));
case "Edm.DateTime": return XmlConvert.ToDateTime(value, XmlDateTimeSerializationMode.RoundtripKind);
case "Edm.Binary": return Convert.FromBase64String(value);
case "Edm.Guid": return new Guid(value);

default: throw new NotSupportedException("Not supported type " + type);
}
}

ответил(а) 2012-05-22T18:03:00+04:00 8 лет, 6 месяцев назад
60

Используйте DynamicTableEntity как тип объекта в ваших запросах. В нем есть словарь свойств, который вы можете найти. Он может возвращать любой тип сущности.

ответил(а) 2014-11-11T01:12:00+03:00 6 лет назад
59

Другой вариант, конечно, состоит в том, чтобы иметь только один тип сущности для таблицы, запрашивать таблицы параллельно и объединять результат, отсортированный по метке времени.
В конечном итоге это может оказаться более разумным выбором в отношении масштабируемости и ремонтопригодности.


В качестве альтернативы вам нужно будет использовать некоторый вкус общих объектов, как описано в "dunnry", где не общие данные не указаны явно и вместо этого сохраняются через словарь.


Я написал альтернативный клиент хранения таблиц Azure, Lucifure Stash, который поддерживает дополнительные абстракции над хранилищем таблиц azure, включая , сохраняющийся в/из словаря, и может работать в вашей ситуации, если это направление вы хотите преследовать.


Lucifure Stash поддерживает большие столбцы данных > 64 Кбайт, массивы и списки, перечисления, составные ключи, сериализацию из коробки, определяемые пользователем морфинг, общедоступные и частные свойства и поля и многое другое. Он доступен для личного использования на http://www.lucifure.com или через NuGet.com.


Изменить: теперь откройте источник CodePlex

ответил(а) 2012-05-22T19:20:00+04:00 8 лет, 6 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема