如何使用Azure Table Storage的单个查询检索多种类型的实体?

我正在尝试了解Azure表存储如何工作以创建Facebook样式的源,我仍然坚持如何检索条目。

(我的问题几乎与https://stackoverflow.com/questions/6843689/retrieve-multiple-type-of-entities-from-azure-table-storage相同,但答案中的链接已被破坏。)

这是我的预期方法:

  1. 为我的应用程序中的所有用户创建个人订阅源,其中包含不同类型的条目(通知,状态更新等)。 我的想法是将它们存储在按每个用户的分区键分组的Azure表中。

  2. 检索同一分区键中的所有条目,并根据条目类型将其传递给不同的视图。

如何在保留其唯一属性的同时查询所有类型的相同基类型的表存储?

CloudTableQuery需要一个类型化实体,如果我将EntryBase指定为generics参数,则不会获得特定于条目的属性( NotificationSpecificPropertyStatusUpdateSpecificProperty ),反之亦然。

我的实体:

 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; } } 

我对Feed的查询:

 List 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 } } 

最后有官方的方式! 🙂

查看NoSQL示例,该示例在Azure存储团队博客的此链接中完成此操作:

Windows Azure存储客户端库2.0表深入了解

有几种方法可以解决这个问题,以及如何做到这一点取决于您的个人偏好以及潜在的绩效目标。

  • 创建一个表示所有查询类型的合并类。 如果我有StatusUpdateEntry和NotificationEntry,那么我只需将每个属性合并为一个类。 序列化程序将自动填充正确的属性,并将其他属性保留为null(或默认值)。 如果您还在实体上放置了“类型”属性(计算或设置为存储),则可以轻松切换该类型。 由于我总是建议在应用程序中将表实体映射到您自己的类型,因此也可以正常工作(该类仅用于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有效。

编辑:正如Lucifure所指出的,另一个很好的选择是围绕它进行设计。 使用多个表,并行查询等。当然,您需要在超时和error handling的复杂性之间进行交换,但这取决于您的需求,这是一个可行且通常很好的选择。

阅读通用实体:

 [DataServiceKey("PartitionKey", "RowKey")] public class GenericEntity { public string PartitionKey { get; set; } public string RowKey { get; set; } Dictionary properties = new Dictionary(); 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(OnReadingEntity); var customers = from o in ctx.CreateQuery(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); } } 

当然,另一个选择是每个表只有一个实体类型,并行查询表并合并按时间戳排序的结果。 从长远来看,参考可扩展性和可维护性,这可能是更谨慎的选择。

或者,您需要使用’dunnry’概述的一些通用实体,其中非公共数据未明确键入,而是通过字典保留。

我编写了一个备用的Azure表存储客户端Lucifure Stash,它支持对azure色表存储的其他抽象,包括持久进出字典 ,如果这是您想要的方向,可能适用于您的情况。

Lucifure Stash支持大型数据列> 64K,数组和列表,枚举,复合键,开箱即用的序列化,用户定义的变形,公共和私有属性和字段等。 它可以在http://www.lucifure.com或NuGet.com免费供个人使用。

编辑:现在在CodePlex开源

使用DynamicTableEntity作为查询中的实体类型。 它有一个你可以查找的属性字典。 它可以返回任何实体类型。