EntityFramework Core 2.2 + Cosmos DB ~ ファーストステップ

1. はじめに

2018年10月(?)あたりからPreview版とはいえ、EntityFramework CoreからCosmos DBにアクセスするプロバイダが提供されていたという事で試してみました。
データの保存と読み込みを行うだけの超基本となるファーストステップの記事になります。

使用した環境

2. こんな事をするよ

すごく単純に EF Core Cosmos DB Provider を使って単純なモデルクラスの保存と読み込みを行います。

3. Cosmos DBの作成

Azureポータルの「+リソースの作成」からCosmos DBを作成します。

f:id:daigo-knowlbo:20190108015649p:plain

f:id:daigo-knowlbo:20190108015756p:plain

4. 実装

以下順に。

4.1. プロジェクト作成

新規プロジェクトを作成します。
コンソール アプリ(.NET Core)

f:id:daigo-knowlbo:20190108012847p:plain

プロジェクト名は「EfCoreCosmosExamConsole」

f:id:daigo-knowlbo:20190108012915p:plain

4.2. Nuget追加

Nugetパッケージで「microsoft.EntityFrameworkcore.Cosmos」を追加します。
プレリリース版を含めるにして検索してインストールします。

f:id:daigo-knowlbo:20190108013743p:plain

4.3. モデルクラス / DbContextクラス追加

Cosmos DBに保存するデータモデルクラスを追加します。
FamilyクラスとPersonクラスとします(家族情報を保存するイメージ)。
※手抜きして Models/Family.cs の1ファイルに2つのクラスを定義しています。

// Models/Family.cs
using System;
using System.Collections.Generic;

namespace EFCoreCosmosExamConsole.Models
{
  public class Family
  {
    public Guid FamilyId { get; set; }

    public Person HeadOfHousehold { get; set; }

    public Person Partner { get; set; }

    public List<Person> Children { get; set; }
  }

  public class Person
  {
    public Guid PersonId { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime Birth { get; set; }
  }
}

FamilyクラスがPersonクラスを世帯主(HeadOfHousehold )、パートナー(Partner)、子供(Children)として保有しています。

DbContextクラスを作成します。
RDBに対するEF Coreの時とほぼ同様の感じです。

// Models/PeopleContext.cs
using Microsoft.EntityFrameworkCore;

namespace EFCoreCosmosExamConsole.Models
{
  public class PeopleContext : DbContext
  {
    public DbSet<Family> Families { get; set; }

    public DbSet<Person> Persons { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      optionsBuilder.UseCosmos(
        "https://ryuichi111cosmos.documents.azure.com:443/",
        "ひみつひみつひみつひみつひみつひみつひみつひみつ",
        "PeopleDatabase"
      );
    }
  }
}

Cosmos DBプロバイダ固有の設定は「OnConfiguring()」での「UseCosmos()」呼び出しになります。
UseCosmos()メソッドは、Microsoft.EntityFrameworkCore.CosmosアセンブリMicrosoft.EntityFrameworkCore.CosmosDbContextOptionsの拡張メソッドとして定義/実装されています。
第1引数には接続先Cosmos DBのURL、第2引数には接続キー、第3引数には(任意の)データベース名を指定します。
「第1引数 接続先Cosmos DBのURL」「第2引数 接続キー」はAzureポータルの対象Cosmos DBのKeysで確認することができます。

f:id:daigo-knowlbo:20190108014227p:plain

ここまでで、ソリューションエクスプローラ的には↓↓↓こんな感じになる。

f:id:daigo-knowlbo:20190108013030p:plain

4.4. main()からDbContext呼び出し

Program.cs main()にCosmos DBへのデータ保存と読み込みを記述します。
RDBに対するEF Core実装とほぼ同じです。

// Program.cs
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using EFCoreCosmosExamConsole.Models;

namespace EFCoreCosmosExamConsole
{
  class Program
  {
    static void Main(string[] args)
    {
      // テスト用の家族オブジェクトを作成(世帯主+パートナー+子供×2)
      Person takashi = new Person() { PersonId = Guid.NewGuid(), FirstName = "Takashi", LastName = "Tanaka", Birth = new DateTime(1985, 4, 5) };
      Person sawako = new Person() { PersonId = Guid.NewGuid(), FirstName = "Sawako", LastName = "Tanaka", Birth = new DateTime(1983, 7, 12) };
      Person chiyori = new Person() { PersonId = Guid.NewGuid(), FirstName = "Chiyori", LastName = "Tanaka", Birth = new DateTime(2001, 10, 4) };
      Person mamoru = new Person() { PersonId = Guid.NewGuid(), FirstName = "Mamoru", LastName = "Tanaka", Birth = new DateTime(2002, 11, 20) };
      Family family = new Family()
      {
        FamilyId = Guid.NewGuid(),
        HeadOfHousehold = takashi,
        Partner = sawako,
      };
      family.Children = new List<Person>();
      family.Children.Add(chiyori);
      family.Children.Add(mamoru);

      // CosmosDBに保存
      using (var context = new PeopleContext())
      {
        // Database / Collectionを(無ければ)作成
        context.Database.EnsureCreated();

        // Familyをコンテキストに追加
        context.Families.Add(family);

        // SaveChanges()の裏側でオブジェクトをJSON変換、シャドウプロパティの追加、CosmosDBへの保存、が行われる
        context.SaveChanges();
      }

      // CosmosDBから読み込み
      using (var context = new PeopleContext())
      {
        // Familyを関連オブジェクトごと取得
        var loladedFamily = context.Families
          .Include(f => f.HeadOfHousehold)
          .Include(f => f.Partner)
          .Include(f => f.Children)
          .Where(f => f.FamilyId == family.FamilyId).FirstOrDefault();

        // Personを単独で取得
        var loadedSawako = context.Persons
          .Where(p => p.PersonId == sawako.PersonId).FirstOrDefault();
      }
    }
  }
}

テスト用に Family / Person モデルクラスの作成

Cosmos DBに保存するテスト用のモデルクラスはべた書きで作成しています。

Cosmos DBの初期化

AzureポータルからCosmos DBの入れ物は作成済みですが、それに紐づく「データベース」「コレクション」がまだ作成されていません。
以下の呼び出しを行うと「データベース」「コレクション」が存在しなければ作成してくれます。

context.Database.EnsureCreated();

ちなみにEnsureCreated()呼び出し直後に、Azureポータル Data Explorer で確認した状態は以下です。

f:id:daigo-knowlbo:20190108020029p:plain

データベース名=PeopleDatabase(PeopleContextにおいてUseCosmos()の第3引数で指定した名称)
コレクション名=PeopleContext(PeopleContextのクラス名)
Throughput(RU) = 400

Familyオブジェクトの保存

以下の呼び出しでCosmos DBにデータを保存することができます(RDBに対するEF Coreと全く同じ)。

context.Families.Add(family);
context.SaveChanges();

以下のように、同一のコレクション(PeopleContext)内に Family / Person オブジェクトが保存されます。

↓↓↓Family f:id:daigo-knowlbo:20190108020510p:plain

↓↓↓Person f:id:daigo-knowlbo:20190108020536p:plain

↓↓↓Person f:id:daigo-knowlbo:20190108020601p:plain

EF Core側の実装における DbContextの括りでコレクションが作成され、そこに対象DbContextが扱うオブジェクトが保存されます。
Cosmos DBにおいては「コレクション=RUの括り(=コスト/パフォーマンス)」であるため、実運用上はコストとパフォーマンスの兼ね合いでDbContextのくくりを検討することになると思います。

それから Family / Person クラスで定義していないプロパティが多数Cosmos DB側のJsonデータには含まれていますが、これらはCosmos DB側で保持するシャドウプロパティになります。

Familyオブジェクト / Personオブジェクトの読み込み

データの読み込みもRDBに対するEF Coreと全く同様です。

// Familyを関連オブジェクトごと取得
var loladedFamily = context.Families
  .Include(f => f.HeadOfHousehold)
  .Include(f => f.Partner)
  .Include(f => f.Children)
  .Where(f => f.FamilyId == family.FamilyId).FirstOrDefault();

// Personを単独で取得
var loadedSawako = context.Persons
  .Where(p => p.PersonId == sawako.PersonId).FirstOrDefault();

まとめ

EF Core Cosmos DB Providerは、RDB操作と非常に類似した(同様の)コードでCosmos DBへのアクセスが可能でいい感じですね。
かつてLINQがデータソースに依存しない(オンメモリオブジェクトであろうがRDBデータであろうが)プログラミングモデルを目指し、実現しましたが、そんなテイストで 相手がRDBであろうがCosmos DBであろうが同様のプログラムコードが書けるのはうれしいです。
もちろんアーキテクチャ的にはバックエンドの技術知識を持つ必要がありますが、すごく期待できるデータプロバイダな気がしました^^

サンプルコードは一応↓↓↓↓↓です。 github.com