Reading and Saving table storage entities without knowing the schema or updating TableStorageEntity schema at runtime


When you have Windows Azure Table
Storage with a schema, you declare a class which inherits from
“TableStorageEntity”  and this class sets the fields which you will use in
your table. Your table schema could be dynamic and during runtime you will be
handling key and values collection , which will be defined in tablestorage
fields. If you want to get table storage entities you can use storage client sample
classes to achieve your objective however a question comes how to update the
table schema during runtime when objects are constructed dynamically. Or you
may ask how to enumerate table properties to construct the proper type of
object dynamically during run time.


After a little research on net, I
found that ReadingEntity and WritingEntity methods can be used to solve it.
Here are the details:


You can define a class that has
PartitionKey, RowKey and a dictionary (maps string to object) and then use
ReadingEntity event on the DataServiceContext to achieve this.


   
[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
“”;  

        }  

    }  



Once you have the entity defined,
you can do your deserialization in the ReadingEntity method.


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

            }  

        } 


The ReadingEntity implementation
is as follows:


        // 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);  

            }  

        }  



Furthermore if you decide to save
the dynamic data, you can use WritingEntity event to store the data. You can
add the xml element for each property to the content element in which the content
element can be retrieved from ReadingWritingEntityEventArgs.


References:

http://social.msdn.microsoft.com/Forums/en-US/windowsazure/thread/481afa1b-03a9-42d9-ae79-9d5dc33b9297/



 

Comments (0)