ravendbravendbを使い始める


備考

このセクションでは、ravendbの概要と、なぜ開発者がそれを使いたいのかを概説します。

ravendb内の大きなテーマについても言及し、関連するトピックにリンクする必要があります。 ravendbのドキュメントは新しくなっているので、それらの関連トピックの初期バージョンを作成する必要があります。

単純なRavenDBコンソールアプリケーション

この例では、 Live Test RavenDBインスタンスを使用します

ここでは、最も基本的な操作を示す簡単なコンソールアプリケーションを作成します。

  • 創造
  • Idによる検索
  • クエリ
  • 更新中
  • 削除

まず新しいVisual Studioソリューションを作成し、それにコンソールアプリケーションプロジェクトを追加します。それをRavenDBDemoConsoleとしましょう。 ReaderがVSコードやお気に入りのエディタを使用している場合、クライアントライブラリの使用法は似ているはずです。

次に、必要な参照を追加する必要があります。 [ソリューションエクスプローラー]ペインの[参照設定]ノードを右クリックし、[NuGetパッケージの管理]を選択します。オンラインで「RavenDb.Client」を参照してください。私は最新の安定版リリースを使用しています。これは、この文書の時点で、3.5.2です。

コードを書いてみましょうか?まず、次のusingステートメントを追加します。

using Raven.Client;
using Raven.Client.Document;
 

これにより、私たちはRavenDBのIDocumentStoreDocumentStore を使用することができます。これは、RavenDBインスタンスに接続するためのインターフェイスとそのすぐれた実装です。これはサーバーに接続するために必要なトップレベルのオブジェクトであり、 RavenDBのドキュメントでは、アプリケーション内でシングルトンとして使用されることを推奨しています。

だから簡単に作成しますが、簡単にするために、シングルトンラッパーを実装しません。プログラムが終了したときに破棄して、接続がきれいに閉じられるようにします。 mainメソッドに次のコードを追加します。

using (IDocumentStore store = new DocumentStore
{
    Url = "http://live-test.ravendb.net",
    DefaultDatabase = "Pets"
})
{
    store.Initialize();
}
 

最初に述べたように、Live Test RavenDBインスタンスを使用して、そのアドレスをDocumentStore Url プロパティとして使用します。デフォルトのデータベース名(この場合は "Pets")も指定します。データベースがまだ存在しない場合、RavenDBはデータベースにアクセスしようとしたときにそれを作成します。存在する場合、クライアントは既存のものを使用できます。 Initialize() メソッドを呼び出す必要があります。

この簡単なアプリケーションでは、オーナーとペットを維持します。 1人の所有者が任意の数のペットを持つかもしれないが、1人のペットは1人の所有者しか持たないので、彼らの接続について考える。実際の世界では、1人のペットには夫や妻のような任意の数の所有者がいるかもしれませんが、ドキュメントデータベースの多対多の関係は、リレーショナルデータベースを使用しており、独自のトピックが必要です。私はこのドメインを選択しました。それは把握するのに十分な共通性があるからです。

ですから、ドメインオブジェクト型を定義する必要があります:

public class Owner
{
    public Owner()
    {
        Pets = new List<Pet>();
    }

    public string Id { get; set; }
    public string Name { get; set; }
    public List<Pet> Pets { get; set; }

    public override string ToString()
    {
        return
            "Owner's Id: " + Id + "\n" +
            "Owner's name: " + Name + "\n" +
            "Pets:\n\t" +
            string.Join("\n\t", Pets.Select(p => p.ToString()));
    }
}

public class Pet
{
    public string Color { get; set; }
    public string Name { get; set; }
    public string Race { get; set; }

    public override string ToString()
    {
        return string.Format("{0}, a {1} {2}", Name, Color, Race);
    }
}
 

ここで注意すべき点がいくつかあります。

まず、 Owner は0個以上のPet 含むことができます。 Owner クラスにはId というプロパティがありますが、 Pet クラスにはId がありません。これは、 Pet オブジェクトがOwner オブジェクトに格納されるためです。これは、この種の関係がリレーショナルデータベースでどのように実装されるかとはかなり異なっています。

これはこのように実装すべきではないと主張するかもしれません - それ正しいかもしれませんが、それは本当に要件に依存します。親指の原則として、 PetOwner なしで存在することが理にかなっている場合、それは埋め込まれるべきではなく、独自の識別子でそれ自身で存在するべきである。私たちのアプリケーションでは、 Pet は所有者がいる場合にのみPet とみなされます。そうでない場合、ペットは動物または動物です。このため、私たちはPet クラスにId プロパティを追加しません。

第2に、所有者クラスの識別子は、RavenDBのドキュメントの例に一般的に示されているように、文字列であることに注意してください。リレーショナルデータベースに使用されている多くの開発者は、これを悪い方法と考えています。これは通常、リレーショナルの世界では意味があります。しかし、RavenDBはLucene.Netを使用してタスクを実行するため、Lucene.Netは文字列操作に特化しているため、ここでは完全に受け入れられます。また、JSONを格納する文書データベースを扱い、基本的にすべてが文字列JSONで

Id プロパティについてもう一つ注意する必要があるのは、必須ではないということです。実際、RavenDBは保存したドキュメントに独自のメタデータを添付しているため、定義しなくてもRavenDBはオブジェクトに問題はありません。しかし、一般的に、より簡単にアクセスできるように定義されています。

コードからRavenDBをどのように使用できるかを見る前に、いくつかの一般的なヘルパーメソッドを定義しましょう。これらは自明でなければなりません。

// Returns the entered string if it is not empty, otherwise, keeps asking for it.
private static string ReadNotEmptyString(string message)
{
    Console.WriteLine(message);
    string res;
    do
    {
        res = Console.ReadLine().Trim();
        if (res == string.Empty)
        {
            Console.WriteLine("Entered value cannot be empty.");
        }
    } while (res == string.Empty);

    return res;
}

// Will use this to prevent text from being cleared before we've read it.
private static void PressAnyKeyToContinue()
{
    Console.WriteLine();
    Console.WriteLine("Press any key to continue.");
    Console.ReadKey();
}

// Prepends the 'owners/' prefix to the id if it is not present (more on it later)
private static string NormalizeOwnerId(string id)
{
    if (!id.ToLower().StartsWith("owners/"))
    {
        id = "owners/" + id;
    }

    return id;
}

// Displays the menu
private static void DisplayMenu()
{
    Console.WriteLine("Select a command");
    Console.WriteLine("C - Create an owner with pets");
    Console.WriteLine("G - Get an owner with its pets by Owner Id");
    Console.WriteLine("N - Query owners whose name starts with...");
    Console.WriteLine("P - Query owners who have a pet whose name starts with...");
    Console.WriteLine("R - Rename an owner by Id");
    Console.WriteLine("D - Delete an owner by Id");
    Console.WriteLine();
}
 

そして私たちの主な方法:

private static void Main(string[] args)
{
    using (IDocumentStore store = new DocumentStore
    {
        Url = "http://live-test.ravendb.net", 
        DefaultDatabase = "Pets"
    })
    {
        store.Initialize();

        string command;
        do
        {
            Console.Clear();
            DisplayMenu();

            command = Console.ReadLine().ToUpper();
            switch (command)
            {
                case "C":
                    Creation(store);
                    break;
                case "G":
                    GetOwnerById(store);
                    break;
                case "N":
                    QueryOwnersByName(store);
                    break;
                case "P":
                    QueryOwnersByPetsName(store);
                    break;
                case "R":
                    RenameOwnerById(store);
                    break;
                case "D":
                    DeleteOwnerById(store);
                    break;
                case "Q":
                    break;
                default:
                    Console.WriteLine("Unknown command.");
                    break;
            }
        } while (command != "Q");
    }
}
 

創造

いくつかのオブジェクトをRavenDBに保存する方法を見てみましょう。以下の一般的なメソッドを定義しましょう:

private static Owner CreateOwner()
{
    string name = ReadNotEmptyString("Enter the owner's name.");

    return new Owner { Name = name };
}

private static Pet CreatePet()
{
    string name = ReadNotEmptyString("Enter the name of the pet.");
    string race = ReadNotEmptyString("Enter the race of the pet.");
    string color = ReadNotEmptyString("Enter the color of the pet.");

    return new Pet
    {
        Color = color,
        Race = race,
        Name = name
    };
}

private static void Creation(IDocumentStore store)
{
    Owner owner = CreateOwner();
    Console.WriteLine(
        "Do you want to create a pet and assign it to {0}? (Y/y: yes, anything else: no)", 
        owner.Name);

    bool createPets = Console.ReadLine().ToLower() == "y";
    do
    {
        owner.Pets.Add(CreatePet());

        Console.WriteLine("Do you want to create a pet and assign it to {0}?", owner.Name);
        createPets = Console.ReadLine().ToLower() == "y";
    } while (createPets);

    using (IDocumentSession session = store.OpenSession())
    {
        session.Store(owner);
        session.SaveChanges();
    }
}
 

さて、どのように動作するか見てみましょう。 Owner オブジェクトを作成し、 Pet オブジェクトを作成して割り当てることをユーザーが望むまで単純なC#ロジックで定義しました。 RavenDBが関係する部分で、この記事の焦点はオブジェクトの保存方法です。

新しく作成されたOwnerPet と一緒に保存するには、まずIDocumentSession を実装するセッションを開く必要があります。ドキュメントストアオブジェクト上でOpenSession を呼び出すことで作成できます。

したがって、差異に注意してください。一方、ドキュメントストアはアプリケーションのライフタイム全体に存在する永続オブジェクトですが、 IDocumentSession は短命で軽量のオブジェクトです。これは、一度に実行する一連の操作を表します(少なくとも、データベースへの少数の呼び出しで)。

RavenDBは、RavenDBのWebサイトで「Client-Server Chatter Protection」と呼ばれるサーバーへのラウンドトリップ回数を過度に避けることを重視しています。このような理由から、セッションには許容されるデータベース呼び出しの数にデフォルトの制限があります。そのため、セッションを開いて処理するときに注意を払う必要があります。この例では、 Owner とそのPet の作成を単独で実行する操作として扱うため、1つのセッションでこれを行い、それを処分します。

私たちにとって興味深いメソッドコールが2つ追加されています。

  • 保存するオブジェクトを登録するsession.Store(owner) 。さらに、オブジェクトのId プロパティが設定されていない場合は設定します。従って、識別子プロパティがId と呼ばれるという事実は、慣例である。
  • session.Savehanges() は実行する実際の操作をRavenDBサーバに送信し、保留中のすべての操作をコミットします。

Idによる検索

別の一般的な操作は、その識別子によってオブジェクトを取得することです。リレーショナル・ワールドでは、通常、識別子を指定するWhere 式を使用してこれを行います。しかし、RavenDBではすべてのクエリが索引を使って行われているため、これは古くなっている可能性があります。実際には、RavenDBはidでクエリを実行しようとすると例外をスローします。代わりに、IDを指定してLoad<T> メソッドを使用する必要があります。私たちのメニューロジックが既に用意されているので、実際に要求されたデータをロードして詳細を表示するメソッドを定義するだけで済みます:

private static void GetOwnerById(IDocumentStore store)
{
    Owner owner;
    string id = NormalizeOwnerId(ReadNotEmptyString("Enter the Id of the owner to display."));

    using (IDocumentSession session = store.OpenSession())
    {
        owner = session.Load<Owner>(id);
    }

    if (owner == null)
    {
        Console.WriteLine("Owner not found.");
    }
    else
    {
        Console.WriteLine(owner);
    }

    PressAnyKeyToContinue();
}
 

RavenDBに関連しているのは、ここでもセッションの初期化であり、次にLoad メソッドの使用です。 RavenDBクライアントライブラリは、タイプパラメタとして指定したタイプとして、逆シリアル化されたオブジェクトを返します。 RavenDBはここで互換性を持たないことを知っておくことが重要です。マッピング可能なプロパティはすべてマップされ、マップできないプロパティはマップされません。

RavenDBは、 Id 前に置かれたドキュメントタイププレフィックスを必要とします。これが、 NormalizeOwnerId の呼び出しの理由です。指定されたIdを持つドキュメントが存在しない場合、 null が返されます。

クエリ

ここでは、2つのタイプのクエリ、つまりOwner ドキュメントの独自のプロパティをクエリするクエリと、埋め込みPet オブジェクトをクエリするクエリの2種類があります。

より簡単なものから始めましょう。そこでは、 Name プロパティが指定された文字列で始まるOwner ドキュメントを照会します。

private static void QueryOwnersByName(IDocumentStore store)
{
    string namePart = ReadNotEmptyString("Enter a name to filter by.");

    List<Owner> result;
    using (IDocumentSession session = store.OpenSession())
    {
        result = session.Query<Owner>()
           .Where(ow => ow.Name.StartsWith(namePart))
           .Take(10)
           .ToList();
    }

    if (result.Count > 0)
    {
        result.ForEach(ow => Console.WriteLine(ow));
    }
    else
    {
        Console.WriteLine("No matches.");
    }
    PressAnyKeyToContinue();
}
 

もう一度、独立した作業としてクエリを実行したいので、セッションを開きます。セッションオブジェクトに対してQuery<TDocumentType> を呼び出すことによって、ドキュメントコレクションをクエリできます。 IRavenQueryable<TDocumentType> オブジェクトを返します。このオブジェクトでは、通常のLINQメソッドやRavenDB固有の拡張機能を呼び出すことができます。ここでは簡単なフィルタリングを行い、 Name プロパティの値は入力された文字列で始まるという条件があります。結果セットの最初の10項目を取り出し、そのリストを作成します。結果セットのサイズを適切に指定することに注意を払う必要があります。ここでは、無制限の結果セット保護と呼ばれるRavenDBによるもう1つの守備措置があります。つまり、(デフォルトでは)最初の128項目だけが返されます。

2番目のクエリは次のようになります。

private static void QueryOwnersByPetsName(IDocumentStore store)
{
    string namePart = ReadNotEmptyString("Enter a name to filter by.");

    List<Owner> result;
    using (IDocumentSession session = store.OpenSession())
    {
       result = session.Query<Owner>()
           .Where(ow => ow.Pets.Any(p => p.Name.StartsWith(namePart)))
           .Take(10)
           .ToList();
    }

    if (result.Count > 0)
    {
        result.ForEach(ow => Console.WriteLine(ow));
    }
    else
    {
        Console.WriteLine("No matches.");
    }
    PressAnyKeyToContinue();
}
 

これははるかに複雑ではありませんが、埋め込まれたオブジェクトのプロパティをクエリすることが自然に可能であることを示すために書いています。このクエリは、名前が入力された値で始まる少なくとも1つのPet を持つ最初の10人のOwner 返します。

削除

削除を実行するには2つの選択肢があります。 1つは文書識別子を渡すことです。これは、オブジェクト自体はメモリに格納されていませんが、識別子を持っていて、データベースへのラウンドトリップが発生しないようにしたい場合に便利です。もう一つの方法は、明らかにRavenDBに保存された実際のオブジェクトを渡すことです。ここで最初のオプションを見てみましょう。もう1つは、他のオーバーロードを使用して適切なオブジェクトを渡すことです。

private static void DeleteOwnerById(IDocumentStore store)
{
    string id = NormalizeOwnerId(ReadNotEmptyString("Enter the Id of the owner to delete."));

    using (IDocumentSession session = store.OpenSession())
    {
        session.Delete(id);
        session.SaveChanges();
    }
}
 

もう一度、作業を行うためにセッションを開く必要があります。前述のように、ここでは、識別子をDelete メソッドに渡して目的のオブジェクトを削除します。 ID接頭辞は、 Load メソッドの場合と同じように、ここに配置する必要があります。実際にdeleteコマンドをデータベースに送信するには、 SaveChanges メソッドを呼び出す必要があります。これは、同じセッションで登録されている他の保留中の操作と同様です。

更新中

最後に、ドキュメントを更新する方法を見ていきます。基本的に、これを行うには2つの方法があります。最初は簡単ですが、ドキュメントをロードし、必要に応じてそのプロパティを更新し、 Store メソッドに渡します。これは読み込みと保存のデモンストレーションによれば簡単なはずですが、いくつか注意する価値があります。

まず、RavenDBクライアントライブラリは変更トラッカーを使用します。これにより、ドキュメントをロードしたセッションがまだ開かれている限り、ドキュメントを実際にStore に渡すことなくドキュメントを更新することができます。この場合、セッションでSaveChanges を呼び出すだけで、更新が行われます。

第2に、これがうまくいくためには、RavenDBが何を更新すべきかを把握できるようにオブジェクトに識別子を設定する必要があります。

これらのことから、私たちは更新の他の方法を見てみましょう。ドキュメントを更新するために使用できるパッチングという概念があります。削除の場合と同様に、独自の使用シナリオもあります。以前のメソッドを使用して更新を実行するのは、すでにオブジェクトをメモリに格納している場合や、型の安全性を使用したい場合に便利です。パッチを使用することは、オブジェクトをメモリにまだ持っていないと、データベースへの不必要なラウンドトリップを回避したい場合にはオプションです。欠点は、単純な文字列を使って更新するプロパティを指定する必要があるためです(LINQマジックでは解決できないものは何もありません)。コードを見てみましょう:

private static void RenameOwnerById(IDocumentStore store)
{
    string id = NormalizeOwnerId(ReadNotEmptyString("Enter the Id of the owner to rename."));
    string newName = ReadNotEmptyString("Enter the new name.");

    store.DatabaseCommands.Patch(id, new Raven.Abstractions.Data.PatchRequest[]{
        new Raven.Abstractions.Data.PatchRequest
        {
            Name = "Name",
            Value = newName
        }
    });
}
 

それはそれを包みます。コードフラグメントをコンソールアプリケーションに貼り付けることで、このサンプルが動作するのを確認することができます。

インストールまたはセットアップ

ravendbをセットアップまたはインストールするための詳しい手順。