しばやん雑記

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

Entity Framework 7 では Azure Table Storage に対応する話

Entity Framework 7 で NoSQL というか KVS に対応すると発表されてましたが、久しぶりにリポジトリを眺めてみると Azure Table Storage 用のプロバイダーが用意されていました。Wiki に vNext ではない .NET Framework 4.5.1 で動かす方法が書いてあったので試してみます。

Home · aspnet/EntityFramework Wiki · GitHub

Home · aspnet/EntityFramework Wiki · GitHub

NuGet のパッケージソースとして MyGet を追加したら、以下のコマンドを叩き込むだけで完了です。

Install-Package EntityFramework.AzureTableStorage -Pre

Entity Framework 7 は基本的に 6 の時と同じように DbContext クラスを使ってテーブルを定義していきます。Table Storage はスキーマレスですが、Entity Framework ではそのままだと扱えないので、入れ物となるエンティティクラスを作る必要があります。

DbContext を継承したクラスでは OnConfiguring メソッドと OnModelCreating メソッドをオーバーライドして、アカウントの設定とエンティティクラスの定義を行います。命名規約に従っていれば PartitionAndRowKey メソッドでの設定は必要ない気がしますが、一応行っておきます。

public class AtsContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }

    protected override void OnConfiguring(DbContextOptions options)
    {
        options.UseAzureTableStorage("ACCOUNT_NAME", "ACCOUNT_KEY");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>(p => p.PartitionAndRowKey(q => q.PartitionKey, q => q.RowKey));
    }
}

public class Customer
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public string Name { get; set; }
    public string ScreenName { get; set; }
}

パッと見は Entity Framework 6 のコードファーストと変わらないコードですね。

そして使う側のコードも今までと特に変化ありません。違いと言えば EnsureCreated メソッドを呼び出して、テーブルが無い場合には作るといった処理を行っているぐらいでしょうか。*1

var context = new AtsContext();

context.Database.EnsureCreated();

context.Customers.AddRange(new[]
{
    new Customer
    {
        PartitionKey = "customer",
        RowKey = "1",
        Name = "抱かれたい男 No.1",
        ScreenName = "kamebuchi"
    },
    new Customer
    {
        PartitionKey = "customer",
        RowKey = "2",
        Name = "かずあきさん",
        ScreenName = "kazuakix"
    },
    new Customer
    {
        PartitionKey = "customer",
        RowKey = "3",
        Name = "えろす師匠",
        ScreenName = "ichii_seiren"
    },
});

context.SaveChanges();

context.Customers.Where(p => p.ScreenName == "kamebuchi");

このコードを実行した結果を載せても面白くないと思うので、実際にこのクエリがどのように実行されるのか Fiddler2 を使ってキャプチャを行いました。

f:id:shiba-yan:20140908230247p:plain

ちゃんと OData のフィルタ式を使った絞り込みを行ってくれています。

それでは次に以下のようなクエリを実行してみます。Table Storage クライアントでは Where の中での StartsWith 呼び出しや OrderBy には対応していなかったので、実行時にエラーになりましたが、Entity Framework ではどうなるのか気になりました。

var result = context.Customers.Where(p => p.ScreenName.StartsWith("ka")).OrderByDescending(p => p.RowKey);

foreach (var customer in result)
{
    Console.WriteLine("{0} - {1}", customer.Name, customer.ScreenName);
}

特にエラーが発生することもなく完了して、意図したとおりの結果が返ってきました。

気になるのが API の呼び出しですが、確認したところ思っていた通りフィルタリングをサーバー側で行わずに、全件取得した後にローカルで処理しているようです。

f:id:shiba-yan:20140908230326p:plain

ちなみに以下のようなクエリを実行した場合には、p.ScreenName == "kamebuchi" の条件だけをサーバー側で行うリクエストが投げられていました

context.Customers.Where(p => p.ScreenName == "kamebuchi" && p.ScreenName.StartsWith("ka"));

Entity Framework 7 の Azure Table Storage 用プロバイダーは Table Storage が対応している条件の場合はサーバー側でフィルタリングを出来るだけ行い、対応していない条件やソートに関してはその後にクライアント側で行う方針のようです。

何も考えずに RDB と同じクエリが使えるようになったのは便利ですが、仕組みを分かっていないと絞り込みなしの全件取得が行われるような処理が連発される可能性もあるので注意が必要ですね。

*1:Database.SetInitializer は無くなったみたい