読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

テーブルストレージに複合型を保存してみた

最近は Azure ばっかり弄ってますが、テーブルストレージに何をどのように保存するかで悩んでいます。しかし、テーブルストレージって保存可能なデータに制約が多いんですよね。

  • byte[]
  • bool
  • DateTime
  • double
  • Guid
  • int
  • long
  • string

プリミティブな型しか保存できないのはまあいいんですが、個人的には独自な POCO クラスとかを保存したいわけですよ。なので TableServiceContext を拡張して実装してみました。

参考にしたのは以下のブログです。JSON は使いたくなかったので、プロパティを親に展開する形で実装しました。

tkg84's devlog: Azure Table Storageに非サポート型のプロパティを保存する

とりあえずコードを載せておきます。

public class ComplexTypeServiceContext : TableServiceContext
{
    public ComplexTypeServiceContext(string baseAddress, StorageCredentials credentials)
        : base(baseAddress, credentials)
    {
        IgnoreMissingProperties = true;

        ReadingEntity += MessageServiceContext_ReadingEntity;
        WritingEntity += MessageServiceContext_WritingEntity;
    }

    private static readonly XNamespace m = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
    private static readonly XNamespace d = "http://schemas.microsoft.com/ado/2007/08/dataservices";

    private static void MessageServiceContext_ReadingEntity(object sender, ReadingWritingEntityEventArgs e)
    {
        var properties = e.Data.Descendants(m + "properties").First();

        foreach (var property in e.Entity.GetType().GetProperties())
        {
            if (TypeDescriptor.GetConverter(property.PropertyType).CanConvertFrom(typeof(string)))
            {
                continue;
            }

            var value = Activator.CreateInstance(property.PropertyType);

            foreach (var childProperty in property.PropertyType.GetProperties())
            {
                var element = properties.Element(d + property.Name + "_" + childProperty.Name);

                childProperty.SetValue(value, element == null ? null : element.Value, null);
            }

            property.SetValue(e.Entity, value, null);
        }
    }

    private static void MessageServiceContext_WritingEntity(object sender, ReadingWritingEntityEventArgs e)
    {
        var properties = e.Data.Descendants(m + "properties").First();

        foreach (var property in e.Entity.GetType().GetProperties())
        {
            if (TypeDescriptor.GetConverter(property.PropertyType).CanConvertFrom(typeof(string)))
            {
                continue;
            }

            var node = properties.Element(d + property.Name);

            if (node != null)
            {
                node.Remove();
            }

            var value = property.GetValue(e.Entity, null);

            foreach (var childProperty in property.PropertyType.GetProperties())
            {
                var element = new XElement(d + property.Name + "_" + childProperty.Name);

                element.SetValue(childProperty.GetValue(value, null));

                properties.Add(element);
            }
        }
    }
}

そして以下のようなクラスを用意します。

public sealed class Message : TableServiceEntity
{
    public string Text { get; set; }

    public User User { get; set; }
}

public class User
{
    public string Name { get; set; }

    public string Description { get; set; }
}

これをさっきのサービスコンテキストを使ってテーブルストレージに保存するとこんな感じです。

ちゃんとデシリアライズも出来るので、個人的には重宝しそうです。