Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a TableEntity with Array or List property?

I have stored in an Azure Table some enumerations like this

pk   rk |    en     fr     de   ...

foo  1  |  'Eune' 'Fune' 'Dune' ...
foo  2  |  'Edoe' 'Fdoe' 'Ddoe' ...

bar  1  |  'Unee' 'Unef' 'Trid' ...
bar  2  |  'Diee' 'Dief' 'Died' ...
bar  3  |  'Trie' 'Tref' 'Trid' ...

en, fr, de etc... are the language codes, and respectively the column names in the table.

What kind of TableEntity should I create in order to load it properly

public class FooEntity : TableEntity
{
    public Dictionary<string, string> Descriptions {get; set} // ?
}

and then use them like myFoo["fr"]... is it possible?

Say I have English GUI and I need to display a Foo select with Eune/Edoe as select values.

like image 439
serhio Avatar asked Aug 05 '17 01:08

serhio


2 Answers

Azure Storage Table doesn't support Array, List or Dictionary as an entity property. You can find all the supported properties types here (section "Property Types").

However, you can consider serializing the array/list/dictionary to a string property, and declare a property with [IgnoreProperty] attribute in your TableEntity class to convert the serialized string back to array/list/dictionary.

public class MyEntity : TableEntity
{
    public string DicPropertyRaw { get; set; }

    [IgnoreProperty]
    public Dictionary<string, string> DicProperty
    {
        get
        {
            return Deserialize(DicPropertyRaw);
        }

        set
        {
            DicPropertyRaw = Serialize(value);
        }
    }
}
like image 188
Zhaoxing Lu Avatar answered Nov 06 '22 03:11

Zhaoxing Lu


My answer extends Zhaoxing's approach of writing the complex entity property to a JSON and persisting that to Azure CosmosDB.

However, serialization between a string and object in the setter causes the following issues:

  1. If, for example, you were to add or remove an item from your dictionary DicProperty, its setter would not get called since you have not modified the dictionary but have modified its contents. Similarly, in more complex use cases where you're interested in serializing custom objects or classes, modifying a member of the class will not trigger the setter. This could result in data being lost when the entity is committed to the CloudTable.
    1. If you do choose to implement something like INotifyPropertyChanged on your complex properties, either by using some form of an ObservableCollection or doing the event notification work yourself, you end up serializing and deserializing far too many times. This is also way too much code throughout your models to be useful.

Instead, I overrode TableEntity's WriteEntity and ReadEntity methods to write custom serialization and deserialization code that is only called when an entity is retrieved from the CloudTable or committed to it -- so only once for each retrieve, update operation etc.

Code below. I've illustrated a more complex example, where my TableEntity contains a class which in turn contains a dictionary.

public class MeetingLayoutEntity : TableEntity
{
    /// <summary>
    ///  Extends TableEntity, the base class for entries in Azure CosmosDB Table tables. 
    /// </summary>
    public MeetingLayoutEntity() { }

    public MeetingLayoutEntity(MeetingLayout layout, string partition, string meetingId)
    {
        this.Layout = layout;
        this.PartitionKey = partition;
        this.RowKey = meetingId;
    }

    // Complex object which will be serialized/persisted as a JSON.
    [IgnoreProperty]
    public MeetingLayout Layout { get; set; }

    public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
    {
        // This line will write partition key and row key, but not Layout since it has the IgnoreProperty attribute
        var x = base.WriteEntity(operationContext);

        // Writing x manually as a serialized string.
        x[nameof(this.Layout)] = new EntityProperty(JsonConvert.SerializeObject(this.Layout));
        return x;
    }

    public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
    {
        base.ReadEntity(properties, operationContext);
        if (properties.ContainsKey(nameof(this.Layout)))
        {
            this.Layout = JsonConvert.DeserializeObject<MeetingLayout>(properties[nameof(this.Layout)].StringValue);
        }
    }

}

Learn more about ReadEntity and WriteEntity.

like image 15
Harshita Gupta Avatar answered Nov 06 '22 02:11

Harshita Gupta