Create a TableEntity with Array or List property?
Asked Answered
B

4

10

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.

Brice answered 5/8, 2017 at 1:48 Comment(0)
S
16

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);
        }
    }
}
Sangfroid answered 7/8, 2017 at 1:41 Comment(1)
works as intended tyMillner
I
16

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.

Irreconcilable answered 10/6, 2018 at 7:7 Comment(0)
C
1

You can write any kind of object to Table storage with ObjectFlattenerRecomposer api Nuget package: https://www.nuget.org/packages/ObjectFlattenerRecomposer/ version 2.0.0 also supports arrays and enumerables. These properties will be transparently converted to json string before being written to table storage and deserialized into original object when read back from Table storage. The api also allows you to write complex objects to table storage as well.

Coelenterate answered 15/12, 2018 at 19:25 Comment(0)
C
0

Use a Custom Attribute to read/write your complex types

This solution has the following advantages:

  1. Your entity object ends up with only one property for your complex type (rather than two when using [IgnoreProperty]).
  2. If you move the ReadEntity and WriteEntity code to a base class, all of the serialization code can be abstracted away from your entity.

This solution is detailed on the web here: https://www.devprotocol.com/azure-table-storage-and-complex-types-stored-in-json/

Github repo: https://github.com/jtourlamain/DevProtocol.Azure.EntityPropertyConverter

If you'd prefer a solution that uses LINQ: https://blog.bitscry.com/2019/04/12/adding-complex-properties-of-a-tableentity-to-azure-table-storage/

Better yet, do it the right way with the latest libraries and use the built-in Flatten and ConvertBack: https://mcmap.net/q/989634/-using-pocos-when-persisting-to-azure-table-storage

Catenary answered 22/5, 2020 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.