kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

Azure Table ストレージをまっとうに使う為のExpressionVisitor


ランチにビールはかまいませんが、画像をアップするのはテロですのでやめましょう。 などと内輪ねたを最初に持ち出してみます。
 
というわけで、Azure の Table ストレージのKVS固有の制約を取っ払う方法についてです。
 
たとえば、商品マスタをAzureテーブルに乗せたとします、商品コード、商品分類、価格、その他いろいろなプロパティで検索を行いたい場合、これをどうやって実装するかです。
 
価格が1000円代のゲームソフトで機種 xbox な物ならこんなLINQ式になるはずです。
 
from prod in db.Products
  where 1000<=prod.price
      && prod.price<2000 
      && prod.type== "game"
      && prod.platform=="xbox"
  select prod
 
RowKey へのキー重畳によって以下のキーフォーマットを持つと設計すれば良いでしょう。
 
RowKey = 商品コード 8文字 + 商品価格(数字8桁) + 商品種別(英字6文字) + プラットフォーム(英字6文字)
 
これにより、以下の LINQ 式が上記LINQと同等になるわけです。
 
from prod in db.Products
  where string.Compare( prod.RowKey.SubString( 8,8 ) , "00001000", StringComparison.Ordinal ) >=0
   && string.Compare( prod.RowKey.SubString( 8,8 ) , "00002000", StringComparison.Ordinal ) <0
   && prod.RowKey.SubString( 16,6 ).Trim() == "game"
   && prod.RowKey.SubString( 22,6 ).Trim() == "xbox"
 
これがTableストレージの REST の GET での fillter 句は以下のようになるはずです。
 
$filter= (substring( RowKey, 8,8 ) ge ‘00001000’) and (substring(RowKey,8,8) lt ‘00002000’) and (trim(substring(RowKey,16,6)) eq ‘game’) and (trim(substring(RowKey,22,6)) eq ‘xbox’)
 
貴方が Azure ストレージ専用のプログラムを書いているなら要するにこういう LINQ を書き散らせば良いわけです。話は終了
キー項目を増やしたくなったとかになった場合でも64KBまでRowKeyは増やせますので、そうすれば良いだけのこと、やったね!
 
んで Azure ストレージ専用のプログラムを書いてない、他でも通るクエリを書きたい場合にどうするのかって事。
 
ここで ExpressionVisitor クラスの登場となります。
(.NET 3.5 ではクラスライブラリに標準実装は無かったんですが、MSDN上にサンプルがありました。.NET 4では標準です)
 
なんかのプロパティ(またはフィールド)にアクセスしている場所は MemberExpression があります。
 
VisitMember をオーバライドして以下のようにします。
 
protected override Expression VisitMember( MemberExpression m )
{
    MemberInfo mi = m.Member;
    var keyMap =from attr in mi.GetCustomAttributes( typeof( KeyMapAttribute ) ).Cast<KeyMapAttribute>().SingleOrDefault();
    if( keyMap==null ) return base.VisitMember( MemberExpression m );
// 本当はいろいろやるんだけど要するに以下をする
    Expression instance = Expression.MemberAccess( m.Expression, m.Expression.Type.GetMember( keyMap.PropertyName ) )
    retrun Expression.Call( Expression.Call( 
                instance,
                typeof(string).GetMethod( "SubString", new Type[] { typeof(int), typeof(int) } ),
                new Expression[] { Expression.Constant(keyMap.BeginPos), Expression.Constant(keyMap.Length) } )
        , typeof(string).GetMethod( "Trim", Type.EmptyTypes ), new Expression[] {} );
}
 
KeyMapAttribute は以下の定義
 
[AttributeTarget(Property)]
class KeyMapAttribute : Attribute
{
    public string PropertyName { get; private set; }
    public int BeginPos { get; private set; }
    public int Length { get; private set; }
 
    public KeyMapAttribute( string propertyName, int beginPos, int length )
    {
        PropertyName = propertyName;
        BeginPos = beginPos;
        Length = length;
    }
}
 
これで
[KeyMap( "RowKey", 16, 6 )]
public string type { get; set; }
 
としとけば上記のExpressionVisitor が where prod.type == "hoge" の prod.type 部を prod.RowKey.SubString( 16,6 ).Trim() に変換してくれるわけですね。
 
これで普通の LINQ 式でのwhere がキー重畳を意識して RowKey なり PartitionKey を使った格好に書き換えられますので、別のテクノロジ(たとえば LINQ to SQL)向けに書かれた LINQ 式でもかなりが通るようになるわけです。
 
このように Azure テーブルの標準のTableClientはぶっちゃけていえば機能不足というか「ストレージテクノロジの基本特性が露出した状態」にありますので、まだまだアプリケーションで素のまま使うにはアレです。「式木をゴリゴリ操作するなんて怖いこと僕できない」な人にはぶっちゃけまだ手を出すには早い素材と言えるかも知れません。(=人柱希望者でない人は傍観推奨)
 
式木をゴリゴリするのが苦にならない or 苦しかろうが LINQ ですでに書かれたクエリ重要な人はこの辺りいろいろ変換をかける ExpressionVisitor をいろいろ書き散らすつもりで Azure に取り組んだほうがよろしいと思います。
まぁ、うちのばあい、(最近事例が追加で出たみたいなんですが)、CMSの中核は「LINQ クエリを実行して結果をHTMLレンダリングするテンプレートエンジン」だったりして、LINQクエリは「顧客に提供済みのプロダクト上での顧客資産」なわけで、Azureで動かすからといって「KVSだから」うんぬん言うのも無駄な話、できなければ、できるようにするしかないのであります。
広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。