This is not the current version. View the latest documentation

Realm Xamarinはアプリケーションのモデル層を効率的に安全で迅速な方法で記述することができます。 下記の例をご覧ください:

// 通常のC#のクラスと同じように定義します
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

var realm = Realm.GetInstance();

// 検索にはLINQクエリが使用できます
var puppies = realm.All<Dog>().Where(d => d.Age < 2);

puppies.Count(); // => 0 because no dogs have been added yet

// トランザクションを用いてオブジェクトを保存・更新します
realm.Write(() =>
{
    realm.Add(new Dog { Name = "Rex", Age = 1 });
});

// 検索結果は自動的にリアルタイムに更新されます
puppies.Count(); // => 1

// LINQクエリの構文はほとんど同じように使えます
var oldDogs = from d in realm.All<Dog>() where d.Age > 8 select d;

// 別のスレッドで検索や更新をすることもできます
new Thread(() =>
{
    var realm2 = Realm.GetInstance();

    var theDog = realm2.All<Dog>().Where(d => d.Age == 1).First();
    realm2.Write(() => theDog.Age = 3);
}).Start();

はじめに

Realmを使用するにはNuGetを利用してインストールします。ソースコードはGitHubにあります。 ナイトリービルドはMyGet経由で利用できます。

必要条件

下記の開発環境をサポートしています:

  • Visual Studio 2015以上、またはXamarin Studio 6.1.1以上
  • Xamarin.iOS 10.0.1.10以上、iOS 7以上、native UIとXamarin Formsの両方をサポート
  • Xamarin.Android 7.0.1.3以上、API level 10以上、native UIとXamarin Formsの両方をサポート
  • Xamarin.Macは未サポートです

Xamarinの開発は常に進んでいるため、サポートするバージョンはRealmのリリース時のStableアップデートチャネルに基づきます。Xamarinのマイナーバージョンが異なるくらいなら、RealmはXamarinの機能には密接に依存していないので問題になることはまずありません。ただし、XamarinのBetaチャネルまたはAlphaチャネルを使用すると問題が発生する恐れがあります。

PCLを利用している方へ重要なお知らせ - RealmとPCLは「NuGet Bait and Switch Trick」という記事で紹介されているテクニックによって動作します。そのためには、RealmをNuGetを用いて、すべてのRealmを使用しているPCLにインストールする必要があります。

共有プロジェクトを使用している場合は、そのプラットフォームに対応したRealmをNuGetを用いてインストールしてください。

Android ABIサポート

いくつかの命令セットに制限があるので、armeabiABI設定を サポートしていません

Xamarinプロジェクトを新規作成する際のデフォルトテンプレートではデバッグビルドではすべてのABI設定が有効になっていますが、リリースビルドではarmeabiだけが有効になっています。 リリースビルドではこの設定を変更する必要があります

ABI設定を正しく行っていない場合、特定のアーキテクチャのデバイスでSystem.TypeInitializationExceptionが発生することがあります。たとえば、Galaxy Tab S2などの64 bitデバイスにデプロイすると、armeabiarmeabi-v7a有効で、arm64-v8a無効の場合 はそのエラーが発生します。

他のABIをリンクする特別なな理由がなければ、armeabi以外の すべて のABIを有効にすることが最善の方法です。

  • armeabi-v7a
  • arm64-v8a
  • x86
  • x86_64

Xamarin Studioでは、この設定はプロジェクトオプション>ビルド>Androidビルド>詳細タブを右クリックすることで変更できます。

Visual Studioでは、この設定はプロジェクトプロパティ>Androidオプション>詳細タブを右クリックすることで変更できます。

インストール

ナイトリービルドを利用するにはNuGetの取得先にhttps://www.myget.org/F/realm-nightly/を指定します。VS2015またはXamarin Studio 6.1でNuGet バージョン3を使用しているならhttps://www.myget.org/F/realm-nightly/api/v3/index.jsonを指定します。

  1. ソリューションペインのプロジェクトの下にある”Packages”の歯車の形のボタンをクリックし、”Add Packages…“を選択します。
  2. 検索フィールドに”Realm”と入力します。
  3. Realmを選択し、追加します。
  4. Fodyが依存関係として追加されたことを確認します。

Realmパッケージには必要なものがすべて含まれています。RealmはFodyというWeavingのためのライブラリを依存関係として追加します。FodyはRealmObjectを永続化可能にするために必要です。

ここまででパッケージがインストールされます。もし、すでにプロジェクトでFodyを利用している場合は、もともとのFodyWeavers.xmlを更新する必要があります。これはRealmWeaverを含め、すべてのWeavingの設定を使えるようにするために必要な手続きです。

下記はすでにFodyをプロジェクトで使用したいた場合に、Realmの設定を有効にするためのFodyWeavers.xmlの例です。

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <RealmWeaver />
</Weavers>
  1. プロジェクトにて、ソリューションからTools - NuGet Package Manager - Manage Packagesと選択します。
  2. インストール可能なパッケージの一覧から、Realmパッケージを選択します。
  3. 右側にプロジェクトがチェックされていることと、インストールボタンが押せることを確認します。
  4. インストールボタンを押します。
  5. ダイアログが表示されたらOKボタンを押します。このダイアログはRealmとFodyをインストールするかどうかの確認が表示されています。

Realmパッケージには必要なものがすべて含まれています。RealmはFodyというWeavingのためのライブラリを依存関係として追加します。FodyはRealmObjectを永続化可能にするために必要です。

ここまででパッケージがインストールされます。もし、すでにプロジェクトでFodyを利用している場合は、もともとのFodyWeavers.xmlを更新する必要があります。これはRealmWeaverを含め、すべてのWeavingの設定を使えるようにするために必要な手続きです。

下記はすでにFodyをプロジェクトで使用したいた場合に、Realmの設定を有効にするためのFodyWeavers.xmlの例です。

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <RealmWeaver />
</Weavers>

Realm Browser

RealmBrowserは、.realmデータベースを閲覧、編集するMac アプリケーションです。

現在のRealm Xamarinがサポートしているファイルフォーマットは、少し古いフォーマットです。最新のRealm Browserでファイルを開いてしまうと、そのファイルは現在のRealm Xamarinでは開くことができなくなります。上記のリンクは現在のRealm Xamarinと互換性のあるRealm Browserのバージョンです。

Realm Browser

Tools > Generate demo database と選択すると、サンプルデータを含むテスト用のRealmデータベースを作ることができます。

開発中のアプリのRealmファイルがどの場所に格納されているかは、このStackOverflowの回答が参考になります。

Realm BrowserMac App Store からダウンロードできます。

API Reference

Realmで使用できるすべてのクラスとメソッドに関しては、APIリファレンスをご覧ください。

サンプルコード

GitHubリポジトリのexamplesフォルダにたくさんのサンプルコードがあります。ぜひ、ご参考にしてください。

ヘルプ

  • 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
  • さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。(質問は日本語で構いません)
  • バグ報告や機能リクエストについては GitHubのIssuesからご報告ください。

モデル

Realmのデータモデルは、普通のC#のクラスやプロパティとして定義できます。モデルオブジェクトを定義するには、単にRealmObjectのサブクラスを作成するだけです。

Realmのモデルオブジェクトは、他のC#のオブジェクトとほとんど同様に機能します。一般的なクラスと同様にメソッドやイベントを追加し、同じように使用することができます。主な制限はオブジェクトは作成したスレッド以外では使用できないことと、永続化するプロパティはgetterとsetterを生成する必要があることです。

さらに、モデルクラスはPublicかつ引数なしのコンストラクタを持っていなければなりません。カスタムのコンストラクタを1つも定義しなければ、コンパイラが自動的に引数なしのコンストラクタを追加します。 しかし、もし1つでも自分でコンストラクタを定義した場合は、合わせて引数なしのコンストラクタも定義しなければなりません。

関連とネストしたデータ構造は、単純に関連の対象となるモデルクラスのプロパティを持たせる、対象のモデルクラスを保持するIListをプロパティとして持たせます。

// Dog model
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

対応しているデータ型

Realmは符号なし整数型以外のプリミティブ型(boolcharbyteshortintlongfloatdouble)とstring、そしてDateTimeOffset型をサポートしています。Null許容型も同様にサポートしています。詳しくはNull許容型のプロパティセクションで説明しています。

Date型

日付を表す型としてはDateTime型ではなく標準のDateTimeOffset型を採用しています。

このことはMicrosoftによって曖昧さがあるDateTimeよりも推奨されていることに基づいています。

実際の値は100ナノ秒の精度で保存されます。

タイムゾーンを指定してDateTimeOffsetを保存できますが、RealmにはUTCの値が保存されます。他のプラットフォームとの互換性のために曖昧さのない表現を採用しています。タイムゾーン指定子がなくなるので、UtcTicksの代わりに Ticksを使って値を比較すると正しくないことがあります。

リレーションシップ(関連)

RealmObjectはプロパティにRealmObjectやIListを使用して、他のオブジェクトとの関連を定義することができます。

IList型のプロパティについては、 IListプロパティを定義すると最初に使用されたときに実際のインスタンスが作成されます。自分でインスタンスを作成することはできません。そのためget;だけを定義しなければなりません。

1対1のリレーションシップ

モデル間で1対1や多対1の関連を持たせるには、別のRealmObjectのサブクラスのプロパティを定義します。

public class Dog : RealmObject
{
    // ... other property declarations
    public Person Owner { get; set; };
}

public class Person : RealmObject
{
    public string Name { get; set; }
}

関連として定義したプロパティの使い方は、他のオブジェクトと同様に代入するだけです。

realm.Write(() =>
{
    var jim = realm.Add(new Person { Name = "Jim" });

    var rex = realm.Add(new Dog { Owner = jim });
});

関連を解除するには単にnullを代入します。

rex.Owner = null;

RealmObjectをプロパティとして持つとき、ネストされたプロパティには通常のオブジェクトのプロパティと同じ文法でアクセスできます。上記の例では、rex.Owner.Address.Countryとすると、Realmはネストしたオブジェクトを必要に応じて自動的にフェッチします。

1対多のリレーションシップ

1対多の関連を定義するにはIListをプロパティとして定義します。このプロパティにアクセスすると、空のRealmListか、単一のRealmObject型を要素に持つRealmListが返ります。

例えば、PersonクラスがDogオブジェクトを複数持てるように、1対多の関連として“Dogs”を追加するとすると、IList<Dog>をプロパティとして定義するだけです。IListオブジェクトを自分で生成することはできません。 – Realmが自動的にオブジェクトを生成します。関連のオブジェクトについては要素のオブジェクトを追加または削除する操作のみ行うことができます。

public class Dog : RealmObject
{
    public string Name { get; set; }
}

public class Person : RealmObject
{
    // ... other property declarations
    public IList<Dog> Dogs { get; }
}
jim.Dogs.Add(rex);
jim.Dogs.Count();  // => 1 -- nobody but rex

逆方向の関連

Realmの関連は一方通行です。Dogへの1対多の関連であるPerson.Dogsのプロパティと、Personへの1対1の関連であるDog.Ownerの2つのプロパティは、それぞれ互いに独立して動作します。Person.DogsプロパティにDogオブジェクトを追加しても、Dog.OwnerプロパティにPersonのオブジェクトは自動的に追加されたりはしません。手作業でこの2つの関連の整合性を保とうとすることは、間違いを起こしやすく、複雑で冗長さを招くので、Realmでは下記に示すような、逆方向の関連(バックリンクとも呼ばれます)を表現するためのプロパティを使用することができます。

Backlink(逆方向の関連)プロパティを使用することで、関連元のオブジェクトを特定のプロパティを使って取得することができます。例えば、DogクラスにOwnersという自分自身を関連として保持しているPersonオブジェクトをすべて取得するというプロパティを持たせることができます。その方法はOwnersプロパティをIQueryable<Person>型として定義し、[Backlink]属性を用いてOwnersPersonクラスの関連であると指定するだけです。

public class Dog : RealmObject
{
    [Backlink(nameof(Person.Dogs))]
    public IQueryable<Person> Owners { get; }
}

Backlink(逆方向の関連)プロパティはIList<RealmObject>型(1対多の関連)とRealmObject型(1対1の関連)のプロパティのどちらにも使えます。

public class Ship : RealmObject
{
    public Captain Captain { get; set; }
}

public class Captain : RealmObject
{
    [Backlink(nameof(Ship.Captain))]
    public IQueryable<Ship> Ships { get; }
}

Null許容型のプロパティ

stringbyte[]や関連に用いるRealmObjectのような参照型はnull値をとることができます。

int?のようなNull許容型やNull許容型のDateTimeOffset?も完全にサポートしています。

プロパティの永続性をコントロールする

RealmObjectを継承したクラスはFody weaverによってコンパイル時に処理されます。そのとき、すべての自動実装プロパティは永続化の対象であると推定され、Realmの内部ストレージとマッピングするためのsetterとgetterが自動的に生成されます。

プロパティの永続化をコントロールするためのメタデータを付加するC#属性をいくつか提供しています。

プロパティが永続化されないようにするには、[\[Ignored\]](/docs/dotnet/0.82.0/api/reference/Realms.IgnoredAttribute.html)属性を付加します。よくある例としては、画像を保存するときに、実際のデータではなくパスだけを保存するような場合です。

public string HeadshotPath { get; set; }

// 画像データは永続化されず、メモリ上にのみ保持されます
[Ignored]
public Image Headshot { get; set; }

カスタムSetter

カスタムのsetterやgetter実装を持つプロパティは自動的に保存されないプロパティとして扱われます。例えば次のようなバリデーションをsetterに追加することができます。

private string Email_ { get; set; }

// Validating version of persistent Email_ property
public string Email
{
    get { return Email_; }
    set
    {
        if (!value.Contains("@")) throw new Exception("Invalid email address");
        Email_ = value;
    }
}

インデックス付きプロパティ

現在のバージョンでは、文字列と整数型、bool型およびDateTimeOffset型のプロパティはインデックスに対応しています。

プロパティをインデックスに登録することは==Containsを使ったクエリの速度を大幅に向上します。その代わりにオブジェクトを作成する速度は少し遅くなります。

プロパティをインデックスに登録するには、[Indexed]属性をプロパティの定義に追加します。 (例)

public class Person : RealmObject
{
    [Indexed]
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

オブジェクトの自動更新(ライブアップデート)

Realmに管理(保存)されたRealmObjectのインスタンスは常に最新の内部データの状態に自動的に更新されます。つまり、オブジェクトをいちいち再読み込みする必要はありません。プロパティを変更すると、その変更はただちに同じオブジェクトを参照している他のインスタンスに反映されます。

var myDog = new Dog { Name = "Fido", Age = 1 };
realm.Write(() =>
{
    realm.Add(myDog);
});

var myPuppy = realm.All<Dog>().First(d => d.Age == 1);
realm.Write(() =>
{
    myPuppy.Age = 2;
}

myDog.Age; // => 2

このようなRealmObjectの性質は、Realmが高速にかつ効率的に動作するだけでなく、アプリケーションのコードをよりシンプルに、リアクティブにします。例えば、あるRealmオブジェクトのデータを表示しているUIコンポーネントがあるとした場合、そのUIコンポーネントを再描画する前に、いちいち再読み込みしたり、検索し直す必要はありません。

Realmの通知に登録することで、いつRealmのデータが更新されたのかが分かります。アプリケーションのUIを更新すべきタイミングを知ることができます。

プライマリキー・プロパティ

[PrimaryKey]属性はそのRealmObjectモデルクラスのプロパティに 1つにだけ 設定することができます。プライマリキーを定義すると、オブジェクトの検索と更新を効率よく行なうことができ、それぞれの値がユニークであるということも保証できます。

charと整数型、および、文字列型のプロパティのみ、プライマリキーとして指定できます。小さな整数値を指定したり、どの型を使っても、パフォーマンスや保存されるサイズには関係ありません。

複数のプロパティに[PrimaryKey]属性を付加すると、コンパイルされますが実行時に最初のRealmを開く際に Schema validation failed というエラーが発生します。

PrimaryKeyを指定したオブジェクトをRealmに保存した後は、そのオブジェクトIDを変更することはできなくなります。

同じプライマリキーの値を持つ別のオブジェクトを保存しようとすると、RealmDuplicatePrimaryKeyValueException例外が発生します。

プライマリキーが定義されていれば、Realm.Findを使ってオブジェクトをすばやく取得することができます。これは、LINQを使用するよりもより効率的なインデックスを使用したクエリが使われます。文字列、文字または整数キーに対応するためにそれぞれのオーバーロードが定義されています。

public class Person : RealmObject
{
    [PrimaryKey]
    public string SSN { get; set; }
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

var objByPK = realm.Find<Person>("457-55-5462");

保存しないプロパティ

[Ignored]属性を用いて、特定のプロパティをRealmに保存せずに、単なる普通のC#のオブジェクトのプロパティとして扱うことができます。 または、プロパティにSetterもしくはGetterメソッドを定義した場合も、自動的にそのプロパティは保存しないプロパティとして扱われます。

モデルクラスの継承

Realm Xamarinではモデルクラスをさらに継承することはできませんRealmObjectの直接のサブクラス以外に継承されたクラスがあるとWeavingに失敗します。

コレクションクラスについて

Realmオブジェクトの集合を表すには標準の型を使用します。

  1. IQueryable <T>は、クエリを用いて取得されたオブジェクトを表します。
  2. IList <T>は、モデル間の1対多の関係を表すクラスです。

実行時にはどちらもIRealmCollection<T>インターフェースIReadOnlyList<T>INotifyCollectionChangedに準拠し、遅延ロードに対応します。つまり、コレクションのサイズを取得したとしても、実際に要素のオブジェクトがメモリにロードされるのはその要素にアクセスしるときまで遅延されます。

書き込み

Realmオブジェクトに対するすべての変更(追加、変更、削除)は、トランザクションの内部で行う必要があります。

スレッド間でデータを共有したり、アプリキーションの再起動時に以前のデータを利用するには、Realmにデータを保存しなければなりません。これらの操作は、トランザクションの中で行う必要があります。

トランザクションには、無視できないオーバーヘッドが発生しますので、トランザクションの数はできるだけ最小限に抑えることが望ましいです。例えば、ループの中で複数のオブジェクトを追加する場合などは、ループの中で要素ごとにトランザクションを作るのではなく、ループの外に1つだけトランザクションを作る方がパフォーマンスが良くなります。

realm.Write(() =>
{
    var people = Realm.All<Person>();
    foreach (var person in people)
    {
        person.Age += 1;
    }
});

トランザクションはディスクI/Oを伴う操作などと同様に失敗する可能性があります。
ディスクの容量不足などで失敗した際のエラーから復帰するためには例外処理に備える必要があります。簡単にするためにこのドキュメントやサンプルコードではエラー処理をしていませんが、実際のアプリケーションでは、正しくエラーを処理するべきです。

トランザクションを開始するには2つの簡単な方法があります。[Realm.BeginWrite()](/docs/dotnet/0.82.0/api/reference/Realms.Realm.html#Realms_Realm_BeginWrite)[Realm.Write()](/docs/dotnet/0.82.0/api/reference/Realms.Realm.html#Realms_Realm_Write_System_Action_)です。
最初の方法、[Realm.BeginWrite()](/docs/dotnet/0.82.0/api/reference/Realms.Realm.html#Realms_Realm_BeginWrite)[Transaction](/docs/dotnet/0.82.0/api/reference/Realms.Transaction.html)オブジェクトを返します。`Transaction`オブジェクトは`Dispose`パターンを実装しているので、`using`とともに使うことができます。

```csharp
using(var transaction = realm.BeginWrite())
{
    person.FirstName = "John";
    person.Age = 56;
    transaction.Commit();
}

明示的にCommitメソッドをトランザクションオブジェクトに対して呼び出す必要があります。そうしなければ、トランザクションは自動的にロールバックされます。もう一つの方法は、Realm.Write()を用いて変更を加えるコードを囲む方法です。

realm.Write(() =>
{
    person.FirstName = "John";
    person.Age = 56;
});

この方法では、例外が起こらない限りはトランザクションは暗黙的にコミットされます。

オブジェクトの生成

RealmObjectのサブクラスとして定義したモデルクラスは、インスタンス化してRealmに保存することができます。例として下記のようなシンプルなモデルを考えます。

public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
}

インスタンスをRealmの保存するにはRealm.Add()メソッドを使用します。保存されたインスタンスは自動更新されるようになります。

realm.Write(() =>
{
    var myDog = new Dog();
    myDog.Name = "Rex";
    myDog.Age = 10;
    realm.Add(myDog);
});

オブジェクトをRealmに追加した後は、オブジェクトに対するすべての変更は保存されます。(オブジェクトの変更は、トランザクションの中で行わなければなりません)。別のスレッドで、同じRealmに保存されているオブジェクトに変更があった場合は、トランザクションが完了した時点ですべての変更が適用されます。

同時に発生した書き込み処理は、互いの書き込み処理をブロックします。 これは類似の他のデータベースでも同様で、よく使われるベストプラクティスとして、書き込み処理を別のスレッドに分けることを推奨します。

RealmはMVCCアーキテクチャーを採用しているので、書き込み処理の最中でも読み込み処理をブロックすることはありません。同時に複数のスレッドから書き込みをするのでなければ、大きな単位でトランザクションを使いましょう。細かいトランザクションを使うよりこの特性を活かすことができます。

オブジェクトの更新

オブジェクトを更新するには、トランザクションの中でプロパティに値を代入します。

// トランザクションを開始して、オブジェクトを更新します
using (var trans = realm.BeginWrite())
{
    author.Name = "Thomas Pynchon";
    trans.Commit();
}

[PrimaryKey]を定義したオブジェクトについては、realm.Addメソッドの追加の引数としてupdate: trueを渡すことで、既存のオブジェクトを上書きできます。

public class Person : RealmObject
{
    [PrimaryKey]
    public int Id { get; set; }

    // ... other property declarations
}

realm.Write(() =>
{
    realm.Add(new Person
    {
        Id = 1,
        Name = "Kristian"
    });
});

var kristianWithC = new Person
{
    Id = 1,
    Name = "Christian"
};

realm.Write(() => realm.Add(kristianWithC, update: true));

update: trueを指定したにもかかわらず、PrimaryKeyを持たないオブジェクトを渡した場合は、上書きするオブジェクトを見つけられないので、update: falseを指定した場合と同じ挙動になります。 更新対象のオブジェクトが別のRealmObjectを関連として持っている場合、PrimaryKeyがある場合は追加または更新されます。PrimaryKeyが無い場合は単に追加されるだけになります。

var kristian = new Person { Id = 1, Name = "Kristian" };
var rex = new Dog { Id = 1, Name = "Rex" };
kristian.Dogs.Add(rex);

realm.Write(() => realm.Add(kristian));

var christian = new Person { Id = 1, Name = "Christian" };
christian.Dogs.Add(new Dog { Id = 1, Name = "Bethoven" });

realm.Write(() => realm.Add(christian, update: true));

var newName = kristian.Name; // Christian
var newDogName = kristian.Dogs.First().Name; // Bethoven

オブジェクトの削除

オブジェクトを削除するには、トランザクションの中で削除したいオブジェクトをRealm.Removeに渡します。

var cheeseBook = realm.All<Book>().First(b => b.Name == "Cheese");

// トランザクションを開始してオブジェクトを削除します
using (var trans = realm.BeginWrite())
{
    realm.Remove(cheeseBook);
    trans.Commit();
}

Realmに保存されてるオブジェクトをすべて削除することもできます。ディスクスペースを効率的に再利用するために、Realmファイルのサイズはそのまま維持されることに注意してください。

クエリ

クエリは標準のLINQ構文を実装しています。

まず[realm.All](/docs/dotnet/0.82.0/api/reference/Realms.Realm.html#Realms_Realm_All__1)メソッドに型を指定して、その型の全てのオブジェクトを取得します。これがもっとも基本的なクエリになります。それに対して、`Where`やその他のLINQ演算子を繋げて、結果をフィルタすることができます。

Realmで使えるLINQ構文についてはLINQサポートページをご覧ください。

拡張構文とメソッドチェーン

var oldDogs = realm.All<Dog>().Where(d => d.Age > 8);

クエリ式

var oldDogs = from d in realm.All<Dog>() where  d.Age > 8 select d;

どちらの構文を利用した場合でも返されるオブジェクトはIQueryableインターフェースに準拠しています。そのため、下記のようにオブジェクトをイテレーションすることができます。

foreach (var d in oldDogs)
{
    Debug.WriteLine(d.Name);
}

より複雑なクエリのサンプル

もしLINQクエリの使い方にあまり慣れていない場合のために、下記にいくつかの基本的なクエリ構文を示します。 拡張構文 を用いて基本的なクエリを書く方法がわかります。

下記はPersonオブジェクトからJohnまたはPeterというファーストネームを持つオブジェクトをすべて検索する例です。

var johnsAndPeters = realm.All<Person>().Where(p =>
    p.FirstName == "John" ||
    p.FirstName == "Peter");
var peopleList = johnsAndPeters.ToList();

最初の文に書かれているjohnsAndPetersという変数のオブジェクトはIQueryableを実装しています。”John”または”Peter”というファーストネームを持つオブジェクトが格納されています。

これが標準的なLINQクエリの実装です。クエリによって条件に合うオブジェクトを取得することができます。クエリは遅延実行されるので、結果のオブジェクトをループしたり、件数を取得しようとしなければ実際の処理は何も実行されることはありません。

最後に呼び出しているToListメソッドで、Realmコアエンジンからのマッピングを切り離しています。

オブジェクトはコピーされません。条件に合うオブジェクトの参照を取得します。

すべてのオブジェクトをリストとして取得する代わりに、クエリの結果オブジェクトをC#標準のforeach分を用いてループすることもできます。

foreach (var person in johnsAndPeters) // iterate query
{
    Debug.WriteLine(person.Name);
}

論理演算子

C#標準の論理演算子をLINQクエリで利用できます。

カッコを使用して、ネストした条件や優先順位を示すことができます。

var complexPeople = realm.All<Person>().Where(p =>
    p.LastName == "Doe" &&
    (p.FirstName == "Fred" || p.Score > 35));

型を指定してオブジェクトを取得する

指定した型のオブジェクトをすべて取得するには、[realm.All()](/docs/dotnet/0.82.0/api/reference/Realms.Realm.html#Realms_Realm_All__1)メソッドを使用します。指定したすべてのモデルクラスを保持した`IQueryable`コレクションオブジェクトが返ります。上記の例では`Person`クラスのオブジェクトすべてが返ります。

さらに取得したオブジェクトを絞り込むために重ねてLINQクエリを適用することができます。遅延実行されるので、コレクションをループしたりしない限りは何のオーバーヘッドもありません。

var ap = realm.All<Person>();  // this is all items of a type
var scorers = ap.Where(p => p.Score > 35);  // restrict with first search clause
var scorerDoe = scorers.Where(p => p.LastName == "Doe");  // effectively an AND clause

並べ替え

標準のLINQクエリのようにOrderByOrderByDescendingThenByThenByDescendingを用いて、複数レベルの並び順をQueryableに対して指定することができます。

並べ替えは内部クエリエンジンにて非常に効率良く実行されます。並べ替えを実行するためにすべてのオブジェクトをロードすることはありません。また、単にRealm.Allで取得したすべてのオブジェクトを並べ替える場合にはWhereを使う必要はありません。

注意: 取得したオブジェクトをリストに変換するためにToList呼び出した後 に、OrderByなどを用いて並べ替えを行うと 、 _標準のLINQ によりメモリ上で並べ替えが実行されます。ToListを使う場合は、構文の最後で実行するように気をつけてください。

var highestScore = realm.All<Person>().OrderByDescending(p => p.Score).First();

var sortedPeople = realm.All<Person>()
    .OrderBy(p => p.LastName)
    .ThenBy(p => p.FirstName);

クエリの連鎖

取得したオブジェクトはけっしてコピーされず、必要に応じで計算は遅延されるので、複雑なクエリも、非常に効率良くチェーンして分かりやすく書くことができます。

var teenagers = realm.All<Person>().Where(p => p.Age >= 13 && p.Age <= 20);
var firstJohn = teenagers.Where(p => p.FirstName == "John").First();

取得データの数を制限

Realm以外のほとんどのデータベースには検索結果を「ページネーション(ページング)」する仕組みが備わっています(例えばSQLiteの’LIMIT’句によるものなどです)。この仕組みはディスクの過剰な読み込み、あるいは一度に大量のデータをメモリに読み込むことを避けるために使われます。

Realmのクエリは遅延実行されるので、このような「ページネーション」の仕組みはまったく必要ありません。なぜなら、Realmはクエリの実行結果の要素に対して、実際にアクセスしたときだけオブジェクトを読み込むからです。

UIや実装の都合により、クエリの実行結果の一部分だけが必要だったとします。そのときは、単にIQueryableオブジェクトを用いて、必要な要素にだけアクセスすれば良いのです。

LINQが備えているTakeを使いたいと思うかもしれませんが、その機能はまだサポートされていません。使えるようになった際には、一回の操作で要求したすべてのオブジェクトをインスタンス化するものになるでしょう。

Realmについて

Realmとはデータベースと同等の機能を持ち、複数の種類のオブジェクトをファイルとしてディスクに保存することができます。

デフォルトRealm

すでにお気づきかもしれませんが、これまでに示したコード例ではRealm.GetInstance(string optionalPath)optionalPath引数を指定することなく使っています。このStaticメソッドは、Environment.SpecialFolder.Personalが指すパスに作られたdefault.realmという名前のファイルを指すRealmインスタンスをスレッドごとに返します。

Realm Configuration

Realm.GetInstance()を利用することで簡単にRealmを使うことができます。さらにきめ細やかなカスタマイズをしたい場合は、RealmConfigurationオブジェクトを利用します。作成されるRealmの設定をカスタマイズすることができます。

RealmConfigurationを使ってファイルのパスを指定する方法はいくつかあります。(Configurationオブジェクトを一度作成したら後からパスの値を変えることはできません。)

  1. 絶対パスを指定して、ファイルパスを完全に変更する。
  2. 相対パスでディレクトリを指定して、Realmファイルを標準のパスの下層に作成する。
  3. ファイル名だけを指定して、保存場所は変えずにファイル名だけを変更する。

RealmConfigurationオブジェクトではでは、スキーマバージョンを指定できます。 詳細については、マイグレーションのセクションをご覧ください。 開発中に限り、利便性のために既存のファイルと現在のスキーマが一致しないとき(マイグレーションが必要になるとき)に自動的に既存のファイルを削除し、新しいスキーマで作り直す機能があります。その機能を有効にするにはShouldDeleteIfMigrationNeededプロパティをtrueに設定します。すると、Realm.GetInstance()はスキーマが既存のファイルと一致しない場合は既存のファイルを自動的に削除します。リリースする前にモデルの構造を簡単に試行錯誤できます。リリースするアプリではこのプロパティを 有効にしないことを推奨します 。 意図せずデータが消えてしまうという事態を避けるために#if DEBUGセクション内に設定することも良い方法です。

同じConfigurationオブジェクトをRealmをオープンする際に渡すことで、すべてのRealmを同じ設定で使うことができます。この方法は、複数のスレッドで同じRealmを扱うもっとも一般的な方法です。

Configurationオブジェクトを渡して回る代わりに、デフォルトConfigurationをオーバーライドすることで、すべて同じ設定を適用することもできます。

RealmインスタンスはConfiguration単位、スレッド単位のシングルトンであることに注意してください。つまり、Realm.GetInstance()は、同じConfiguration、かつ同じスレッド上で呼び出されるなら同じインスタンスを返します。

Realmインスタンスをクローズする

Realmはネイティブメモリとファイルディスクリプタの開放を行うためにIDisposableを実装しています。そのため、インスタンスがスコープの外に出たときに、自動的にクローズされます。

Realmファイルの探し方

アプリケーション内のRealmファイルの場所がわからないときは、このStackOverflowの回答を参考にしてください。

補助的に作成されるRealmの関連ファイルについて

Realmでは.realm拡張子を持つメインのRealmファイルとは別に、いくつかの内部的に使用する関連ファイルを自動的に作成します。

  • .realm.lock - Realmファイル開く際の競合を防ぐためのロックとして使われます。
  • .realm.note - スレッド・プロセス間の通知に使用する名前付きパイプのファイルです。

これらのファイルは、.realm拡張子のRealmのデータファイルには何の影響も与えません。またこれらのファイルを(実行中を除いて)削除したり移動したりすることも問題ありません。

Realmに関する問題を報告する際には、これらの関連ファイルを.realm拡張子のRealmのデータファイルと一緒に添付してください。関連ファイルには問題を調査するときに役に立つ情報が含まれています。

モデル定義のサブセット

各Realmファイルで保存されるモデルの定義を使い分けたり、制限したいことがあります。

下記のように、RealmConfigurationオブジェクトのObjectClassesプロパティを用いて、それぞれのRealmに保存されるモデルクラスを制限することができます。

class LoneClass : RealmObject
{
    public string Name { get; set;}
}

var config = new RealmConfiguration("RealmWithOneClass.realm");
config.ObjectClasses = new Type[] {typeof(LoneClass)};

// Realmに保存されるクラスを指定した2つだけにする
config.ObjectClasses = new Type[] {typeof(Dog), typeof(Cat)};

Realmファイルを削除するには

キャッシュを消去する、データをすべてリセットするなど、いくつかのケースにおいて、Realmファイルを完全にディスクから削除することが適していることがあります。

普通のファイルと異なり、Realmのファイルはメモリにマッピングされており、RealmファイルはRealmインスタンスが生存している間、有効でなければなりません。

アプリケーションコードで、Realmの細かなファイルについて意識しなくて済むように、Realm.DeleteRealm(RealmConfiguration)という便利なメソッドを提供しています。

var config = new RealmConfiguration("FileWeThrowAway.realm");
Realm.DeleteRealm(config);
var freshRealm = Realm.GetInstance(config);

ファイルサイズを最適化(コンパクション)する

アプリケーションの使用中に、Realmによって使用されるメモリが断片化され、必要以上に多くの領域が占有される可能性があります。内部ストレージを再編成し、可能ならファイルサイズを減らすために、Realm.Compact(config)を利用します。

var config = new RealmConfiguration();
var initialSize = new FileInfo(config).Length;
if (initialSize > 100 * 1024 * 1024) // 100 MB
{
    Realm.Compact(config);
    var newSize = new FileInfo(config).Length; // Should be less than initialSize
}

コンパクションに関する注意事項

  • コンパクションの対象ファイルを開いているRealmインスタンスがある場合はコンパクションは実行できません。
  • ディスクには同じRealmファイルをコピーできるだけの空き容量が必要です。
  • コンパクションに失敗した場合、Realmファイルはそのままで変更されません。
  • このメソッドは成功するとtrueを返し、他に同じファイルを開いているRealmインスタンスがある場合はfalseを返します。
  • 現時点ではコンパクションを実行した結果のファイルサイズを見積もるためのAPIは提供していません。推奨する方法としては、起動時にファイルのサイズをチェックし、一定の値を超えた場合にコンパクションを実行します。

マルチスレッド

独立したスレッドでRealmを使っている限りは、Realmのオブジェクトはすべて一般的なオブジェクトと同じように扱え、並行処理やマルチスレッドについて気にする必要はありません。 Realmにアクセスするためにロックや排他処理を考える必要はありません(たとえRealmが他のスレッドから同時に更新されるとしても)。そして、データの変更が起こるのはトランザクションブロックで囲まれた範囲だけと考えられます。

Realmは並行処理を簡単に扱えるようにするために、それぞれのスレッドで常に一貫性を保ったデータを返します。数多くのスレッドからRealmを同時に操作したとしても、それぞれのスレッドごとにスナップショットのデータが返され、不整合な状態が見えることがありません。

1つだけ注意しなければならないことは、複数のスレッドをまたいで同じ Realmインスタンス を共有することはできないということです。もし、複数のスレッドで同じオブジェクトにアクセスする必要がある場合は、それぞれのスレッドでRealmインスタンスを取得する必要があります。(そうでなければデータが不整合に見える可能性があります。)

他のスレッドから更新されたデータを反映するには

メインスレッド(またはランループ/Looperスレッドを持つサブスレッド)では、ランループが回るごとに自動的に他のスレッドから更新されたデータが反映されます。それ以外のタイミングでは、その時点のスナップショットのデータが返され、他のスレッドでデータが更新されているかどうかを気にすることなく、常に一貫性のあるデータが見えることになります。

それぞれのスレッドで最初にRealmファイルをオープンしたとき、Realmの状態は最後のコミットが成功した状態にあります。そしてそれは次の更新が反映されるまでそのままです。 Realmはランループが回るたびに自動的に最新のデータに更新されます。 もしスレッドがランループを持っていない(一般的なバックグラウンドスレッド)場合は、最新のデータを反映するためにRealm.Refresh()メソッドを自分で呼ぶ必要があります。

また、トランザクションがコミットされたときも最新のデータが反映されます(Transaction.Commit())。

定期的に行われる最新データの反映が失敗すると、トランザクション履歴が”Pinned”になり、ディスク領域の再利用を妨げます。それはファイルサイズの肥大を招くことがあります。この現象について詳しくは現バージョンにおける制限事項をご覧ください。

スレッド間でオブジェクトを受け渡す

永続化されたRealmRealmObjectRealm.Allメソッドから取得されたIQueryableオブジェクト、またはRealmObjectIListプロパティのオブジェクトは、生成されたスレッド内でなければ利用することができません。 別のスレッドで利用されると例外が発生します。これはRealmがトランザクションを分離するための必要な仕様です。

スレッド間をまたいでオブジェクトを受け渡すための方法を紹介します。例えば、PrimaryKeyを用いてRealmObjectを取得し直す、またはRealmConfigurationを用いて、各スレッドごとにRealmインスタンスを取得します。対象のスレッドで取得したインスタンスは、もともとのスレッドとは別の時点のトランザクションのデータである可能性があるので、注意してください。

複数スレッド間でRealmを使う

同じRealmファイルを複数のスレッドから使う場合は、各スレッドでRealmインスタンスをそれぞれ生成する必要があります。 同じ設定によって作られたRealmインスタンスは、ディスク上で同じものを指します。

複数のスレッド間で、Realmインスタンスを共有することはサポートされていません。 同じRealmファイルにアクセスするRealmインスタンスは、すべて同じ設定(RealmConfiguration)でなければなりません。

Realmは、大量のデータを追加するときには、一つのトランザクション中に複数の一括更新をすることにより、非常に効率よく動作します。

Realmオブジェクトはスレッドセーフではないため、複数のスレッド間で共有することができません。それぞれのスレッド/ディスパッチキューでRealmオブジェクトを生成する必要があります。

通知

Android環境では、ChangeリスナーはLooperスレッドを持つスレッドでのみ動作します。Looperスレッドを持たないスレッドではRealmを自分で呼び出す必要があります。

Realmに対する通知

リスナーに登録しておくと、Realmインスタンスは、他のスレッドやプロセスでトランザクションが完了するたびに、他のRealmインスタンスに対して通知を送ります。

realm.RealmChanged += (s, e) =>
{
    // Update UI
}

現在はRealmChangedによる通知は変更内容の情報は提供されないことに注意してください。変更内容を知る必要があるならオブジェクトに対する通知またはコレクションに対する通知を使用します。

オブジェクトに対する通知

RealmObjectクラスはINotifyPropertyChangedインターフェースに準拠しているので、プロパティの変更を監視できます。プロパティに変更があるたびにPropertyChangedEventメソッドが呼ばれます。

public class Account : RealmObject
{
    public long Balance { get; set; }
}

下記のように、PropertyChangedEventイベントをクラスに対して監視すると、Balanceプロパティに変更があるたびにイベントハンドラが呼び出されます。

var account = new Account();
realm.Write(() => realm.Add(account));
account.PropertyChanged += (sender, e) =>
{
    Debug.WriteLine($"New value set for {e.PropertyName}");
}

realm.Write(() => account.Balance = 10); // => "New value set for Balance"

監視を設定するタイミングは、Realmにオブジェクトを保存する前でも後でもどちらでも構いません。変更のイベントはどちらの場合でも同じように発生します。

これだけでも便利な機能ですが、さらにXamarin.Formsを使ったデータバインディングも可能です。詳細は、XamarinドキュメントのデータバインディングからMVVMへを参照してください。

コレクションに対する通知

[Realm.All()](/docs/dotnet/0.82.0/api/reference/Realms.Realm.html#Realms_Realm_All__1)メソッド、または`IQueryable`LINQクエリから取得した`IQueryable`オブジェクトはは自動更新されます。常に最新の状態に自動的にアップデートされるということです。アクセスしたときには常に最新の変更が反映された値が取得されます。

同様に、RealmObjectIListプロパティは、自動更新される1対多の関連です。つまり、アクセスするたびに、関連オブジェクトの最新のコレクションが取得されます。

これらのコレクションの実際のオブジェクトは[IRealmCollection](/docs/dotnet/0.82.0/api/reference/Realms.IRealmCollection-1.html)に準拠しています。つまり変更を監視するための2つの仕組みが使えることを意味しています。Realmでは便利な拡張メソッドとして[AsRealmCollection](/docs/dotnet/0.82.0/api/reference/Realms.CollectionNotificationsExtensions.html#Realms_CollectionNotificationsExtensions_AsRealmCollection__1_System_Collections_Generic_IList___0__)を提供しています。このメソッドを用いて`IList`または`IQueryable`を`IRealmCollection`にキャストできます。

.NET標準のINotifyCollectionChangedを使用するとMVVMデータバインディングの仕組みとうまく連携できます。[IRealmCollection](/docs/dotnet/0.82.0/api/reference/Realms.IRealmCollection-1.html)をビューに直接渡すことができます。

UITableView ListViewを直接(例えばXamarin Native UIプロジェクトで)操作するのに便利な、より詳細な変更情報が必要なら、SubscribeForNotifications拡張メソッドを使用します。このメソッドには、変更セットを使用して呼び出されるデリゲートから追加、削除、または変更されたことを取得できます。

realm.All<Person>().SubscribeForNotifications ((sender, changes, error) =>
{
    // Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});

マイグレーション

データベースを使ってる場合、時間が経つにつれ、データモデルは変更されていくものです。 Realmでのデータモデルは、標準的なC#インターフェースで定義されていますので、インターフェースに変更を加えるだけで、簡単にデータモデルを変えられます。 例えば次のようなPersonモデルがあるとします。

public class Person : RealmObject
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

ここでFirstNameプロパティとLastNameプロパティを統合して1つのFullNameに変更したくなったとします。その場合モデルのインターフェースを次のように変更します。

public class Person : RealmObject
{
    public string FullName { get; set; }
    public int Age { get; set; }
}

このとき、元のデータモデルを使ってすでにデータを保存していた場合は、既存のRealmファイルとコード上のクラス定義に不一致が発生します。ファイルを開こうとした際に不一致が検出されると例外が発生します。その場合はマイグレーションを実行しない限りは続行できません。

マイグレーションを実行する

スキーマの不一致を解決するもっとも簡単な方法はスキーマバージョンを上げることです。スキーマバージョンを指定しなかった場合は自動的にゼロがセットされています。モデル定義を変更した場合は、スキーマバージョンを前よりも大きな値にセットします。スキーマバージョンの指定はConfigurationオブジェクトのプロパティに設定します。これはモデルの変更が意図的かどうかを判断するために必要な作業になります。

var config = new RealmConfiguration() { SchemaVersion = 1 };

var realm = Realm.GetInstance(config);

この状態で既存のファイルを開くと、PersonモデルのFirstNameプロパティとLastNameプロパティに記録されたデータはすべて削除され、新しくFullNameプロパティが追加されます。FullNameプロパティには空の文字列が設定されます。Ageプロパティは以前のデータがそのまま残っています。おそらく本当に実現したいことは古いデータを移行することでしょう。そのためにはRealmのマイグレーションハンドラにデータを移行する処理を書かなければなりません。マイグレーションハンドラはMigrationCallback関数を用います。この関数はMigrationオブジェクトを引数として受け取ります。Migrationオブジェクトは2つのRealm型のプロパティ、OldRealmNewRealmを持ちます。これらを使って古いデータモデルから新しいデータモデルにデータをコピーする処理を記述します。

クラスやプロパティの名前を変更した場合も、Realmは自動的に何が変更されたかは検知できないので、通常はマイグレーション処理を記述する必要があります。

このコールバック関数は古いスキーマバージョンのファイルを開く際に1回だけ呼び出されます。複数のバージョンに渡るマイグレーションがある場合は、すべてのバージョンに対してマイグレーションを行う必要があります。

この例では新しいPersonクラスはFirstNameプロパティとLastNameプロパティが無くなっているので、そのデータに通常の方法ではアクセスできません。そこで動的APIを利用します。

var config = new RealmConfiguration
{
    SchemaVersion = 1,
    MigrationCallback = (migration, oldSchemaVersion) =>
    {
        var newPeople = migration.NewRealm.All<Person>();

        // Use the dynamic api for oldPeople so we can access
        // .FirstName and .LastName even though they no longer
        // exist in the class definition.
        var oldPeople = migration.OldRealm.All("Person");

        for (var i = 0; i < newPeople.Count(); i++)
        {
            var oldPerson = oldPeople.ElementAt(i);
            var newPerson = newPeople.ElementAt(i);

            newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
        }
    }
};

var realm = Realm.GetInstance(config);

さらにアプリが更新されて、モデルに対して別の変更が行われスキーマバージョンを増加させたとします。アプリがすべて最新の状態に更新されているとは限らないので、oldSchemaVersionパラメータをチェックして、必要なマイグレーションを順番に実行しなければなりません。

ここではPersonクラスにさらに変更を加え、Birthdayプロパティを追加したとします。

public class Person : RealmObject
{
    public string FullName { get; set; }
    public int Age { get; set; }
    public DateTimeOFfset Birthday { get; set; }
}

上記は変更したモデルです。マイグレーションの処理は下記のようになります。

var config = new RealmConfiguration
{
    SchemaVersion = 2,
    MigrationCallback = (migration, oldSchemaVersion) =>
    {
        var newPeople = migration.NewRealm.All<Person>();
        var oldPeople = migration.OldRealm.All("Person");

        for (var i = 0; i < newPeople.Count(); i++)
        {
            var oldPerson = oldPeople.ElementAt(i);
            var newPerson = newPeople.ElementAt(i);

            // Migrate Person from version 0 to 1: replace FirstName and LastName with FullName
            if (oldSchemaVersion < 1)
            {
                newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
            }

            // Migrate Person from version 1 to 2: replace Age with Birthday
            if (oldSchemaVersion < 2)
            {
                newPerson.Birthday = DateTimeOffset.Now.AddYears(-(int)oldPerson.Age);
            }
        }
    }
};

var realm = Realm.GetInstance(config);

暗号化

Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.

Realmでは64バイトの暗号化キーを用いてAES-256とSHA-2暗号化方式でデータベースファイルを暗号化する機能を提供しています。

var config = new RealmConfiguration("Mine.realm");
config.EncryptionKey = new byte[64] // キーは必ずこの大きさでなければなりません
{
  0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
  0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
  0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
  0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
  0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
  0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
  0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
  0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78
};

var realm = Realm.GetInstance(config);  // "Mine.realm"というファイル名で暗号化されたRealmファイルを作成します

この機能を使用すると、ディスクに保存されるデータが透過的にAES-256で必要に応じて暗号/複合化され、SHA-2 HMACによって検証されます。

暗号化したRealmファイルのインスタンスを作成するには同じ暗号化キーが必要になります。

アプリケーションごとに異なるキーを使ってください。 けっして 上記のサンプルコードをコピー&ペースト しないでください 。ユーザーごとに異なるキーを使用したい場合は、Xamarin.Auth APIを参考にしてください。

暗号化したRealmファイルを開く際に、間違ったキーを指定したりキーを渡さなかった場合は、GetInstanceを呼ぶタイミングでRealmFileAccessErrorExceptionが発生します。

暗号化したRealmを使う場合、わずかにパフォーマンスが下がり(10%未満)ます。

既知の制限事項

Realm Xamarinは現在ベータ版であり、1.0のリリースに向けて、頻繁に昨日の追加や不具合の修正が行われています。ベータ版の間は以下の制限があります。

さらに網羅的に現在の制限を知りたい場合は、GitHub issuesをご覧ください。

これらの未実装の機能は正式版のリリースまでには実装されます。

一般的な制限事項

Realmは、柔軟性とパフォーマンスのバランスをうまく保つため、保存するデータに対していくつかの制限事項があります。

  1. クラス名は57文字が上限です。UTF-8の文字をサポートしています。超えている場合は例外が発生します。
  2. プロパティ名は63文字が上限です。UTF-8の文字をサポートしています。超えている場合は例外が発生します。
  3. iOSにおける制限: 各Realmファイルのサイズは、アプリケーションごとにに割り当てられるメモリサイズを超えてはいけません。割り当てられるメモリサイズは、デバイスごとに異なり、実行時のメモリの断片化にも依存します。(詳しくは、rdar://17119975 をご覧ください)それ以上のデータを保存される場合は、Realmファイルを複数に分割してください。

ファイルサイズと中間データについて

SQLiteなどにデータを保存した時より、ディスクの使用容量が少なくなることを期待されることかと思います。 Realmファイルの容量が考えているよりも大きい場合はRealmObjectは古い履歴データを残している可能性があります。

データの一貫性を保つために、Realmは最新のデータにアクセスしたときのみ履歴をアップデートします。 このことは、別のスレッドが多くのデータを長い時間をかけて書き込んでいる最中にデータを読み出そうとした場合、履歴はアップデートされずに古いデータを読み出すことになります。結果として、履歴の中間データが増加していくことになります。

この余分な領域は、最終的には再利用されるか消去されます。

空き領域をすぐに消去したい場合は、コンパクションのセクションを参照してください。

FAQ

一般的な質問

Realmをプロダクション環境で使うことはできますか?

Realmは、2012年から商用のプロダクトで利用されています。これはXamarin C#版の提供は2015年の12月からですが、すべてのRealmプロダクトは同じC++のデータベースエンジンを使用していることによります。

現在のRealmを利用する場合は、RealmのC#のAPIが、コミュニティのフィードバックを受けて変わりうるものだとしてお使いください。 機能追加、不具合の修正も同様に考えてください。

Is Realm open source?

はい。C++で書かれた内部ストレージエンジン、および各言語のSDKは完全にオープンソースソフトウェアとしてApache 2.0ライセンスの元に公開されています。 拡張コンポーネントであるRealm Platformのソースコードは非公開ですが、このコンポーネントはRealmを組み込みデータベースとして使う場合には必須ではありません。

アプリケーションを起動したときにMixpanelへの通信が発生しているのを確認しましたが、なぜでしょうか?

Realmは匿名の統計データを収集しています。統計データの送信は、Realmが[RealmObject](/docs/dotnet/0.82.0/api/reference/Realms.RealmObject.html)クラスをWeavingする際に行われます。送信するデータに個人を特定する情報は一切含まれません。データにはRealmのバージョン、(iOS、OS Xなどの)プラットフォーム、プログラミング言語、などの情報が含まれ、それはRealmの製品の向上にのみ利用されます。統計データの送信は、リリースビルド、またはデバイス上で動作している時には行われません。あくまでも、ビルド時にFodyがWeavingを実行する時だけ送信されます。実際にどのようなデータを収集しているのかは、ソースコードから確認していただけます。

RealmはPCL(ポータブル・クラス・ライブラリ)として動作しますか?

実行ファイルを作るにはiOSまたはAndroidのそれぞれのバージョンのRealmを利用する必要があります。しかし、Realm APIに対してコンパイルできるPCLを提供しています。これは「NuGet Bait and Switch Trick」という記事で紹介されているテクニックです。PCLプロジェクトにNuGetを用いてRealmを追加し、それを実行ファイルに追加すれば動作します。

任意の.NETプラットフォームでRealmを使用できるわけではありません 。RealmネイティブのC++ coreに依存しているので、コアを各プラットフォームに移植する必要があるからです。

Sync

Realm Mobile Platformは、Realm Mobile Databaseの拡張コンポーネントで、デバイスとサーバー間でデータの自動同期を可能にします。

Xamarinのサポートはまもなく完了します!それまでの間、機能の詳細については、こちらをご覧ください。

トラブルシューティング

クラッシュレポート

私たちは開発者の方にクラッシュレポーターを使用していただくことを推奨しています。Realmの操作のうちの多くは(ディスクI/Oを伴う操作などと同様に)実行時に失敗する可能性があります。そのため、クラッシュレポートを収集することは、何が原因で問題が発生したのかを特定し、エラー処理の改善や不具合の修正に有効です。

多くの商用のクラッシュレポーターはログを収集するオプションを提供しています。この機能を有効にすることを強く推奨します。Realm例外が発生した時や、致命的な状態に陥った際にはメタデータの情報をログに出力しており(そこにユーザーデータは一切含まれていません)、それらのログは我々が問題を調査する際に役に立ちます。

Realmの問題や不具合を報告するには

Realmについて問題を発見した際は、GitHubで問題を報告するかhelp@realm.ioにEメール、またはSlackで報告してください。その際には、こちらで問題を再現できるように、できるだけ多くの情報をあわせて教えてください。

下記に示す情報は問題を解決するために非常に役に立ちます。

  1. あなたが実際にやりたいこと・目的。
  2. 期待している結果。
  3. 実際に発生した結果。
  4. 問題の再現方法。
  5. 問題を再現、または理解できるサンプルコード (そのままビルドして実行できるXcodeプロジェクトだと理想的です)
  6. Realm、Xcode、OS Xのバージョン
  7. (利用している場合は)CarthageやCocoaPodsなどのパッケージマネージャのバージョン
  8. 問題の発生するプラットフォーム(iOSやtvOSなど)、OSのバージョン、アーキテクチャ(例: 64-bit iOS 8.1)。
  9. クラッシュログやスタックトレース。上述のクラッシュレポートの項目も参考にしてください。

クラスにプロパティが1つも無い(No properties in class)という実行時例外が起こる

“No properties in class, has linker stripped it?”というメッセージのSystem.InvalidOperationExceptionが発生することがあります。

バージョン0.77.2以降では、もう少し詳しい情報が表示されます。

判明している原因は2つあります。

  1. Fodyに何か問題が発生していてWeavingされたRealmObjectsが1つも無い
  2. RealmObjectsのプロパティがStripされてしまっていてRealmが空になっている

最初のケースに関しては例外はRealmSchemaからスローされます。詳しくはWeavingに失敗したときのセクションをご覧ください。

2番目のケースに関しては例外はObjectSchemaからスローされます。 Linker BeahviourLink All に設定していてクラス宣言に[Preserve(AllMembers = true)]属性が無い場合に問題が起こることがあると報告されています。リンカーはコード内で明示的に参照されるメンバーのみを保持します。つまり、永続化されるがどこでも参照されないプロパティがある場合、そのスキーマが削除されてデータベースの不一致が発生する可能性があります。 デフォルトの挙動では、Realmはすべてのアセンブリ内のRealmObjectサブクラスの すべて に対するするスキーマを構築します。これは 遅延 されるので、最初にGetInstance()を呼び出すまでは起こりません。これは実行するごとに1回だけ実行され、結果をメモリにキャッシュします。

特定のモデルクラスだけをRealmに保存したい場合は、ObjectClassesでサブセットを指定する方法でGetInstance(myConf)を使ってRealmを作成します。このようにすることで、すべてのクラスに対してスキーマ構築はされなくなるので、例外を回避できます。

それ以外の場合は、未使用のクラスがある場合はPreserve属性を追加してください。

Fody: キャッチされなかった例外が発生する

このビルどエラーは既存のプロジェクトに新しくRealmObjectのサブクラスを追加するだけで容易に発生します。

BuildまたはRunを選択するだけでは、Fody Weaverを使用してビルドされません。

Rebuildを選択すると、正しくビルドできます。

Weavingに失敗したとき

ビルドログにクラスがWeavingされなかったという旨の警告メッセージが表示されることがあります。これはWeavingを行うライブラリであるFodyが正しくインストールされていないことを示しています。

まず最初に、FodyWeavers.xmlファイルにRealmWeaverの記述があるかどうか確認してください。

Fodyのインストール時にはソリューションのディレクトリ(通常はプロジェクトディレクトリと同じ階層にあります)のToolsディレクトリにRealmWeaver.Fody.dllがコピーされます。このファイルはどのプロジェクトともリンクされてはいませんが、FodyがDLLをロードするためにはこの場所に存在する必要があります。

Visual Studio 2015とNuGetパッケージマネージャの3.2より古いバージョンの組み合わせで使っていると、Fodyのインストールが失敗することがあります。確認するためには、テキストエディタを用いて、.csprojファイルを開き、Fody.targetsをインポートしている行を見ます。その行は下記のようになっています。

<Import Project="..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets"
    Condition="Exists('..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets')" />

最新のバージョンのNuGetパッケージマネージャにアップグレードすると、この問題は解決します。

これでうまくいかなければ、おそらくFodyとMicrosoft.Bcl.Build.targetsに問題があります。.csprojファイルから次の行を削除すると、直ることがあります。

<Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />

このことについてより詳しくはこちらのStackOverflowの回答をご覧ください。

Realm Coreのダウンロードに失敗する場合

Realmがビルドされるときには、自動的にRealm Coreライブラリをスタティックライブラリとしてダウンロードし、Realm-Cocoaプロジェクトに組み込みます。

下記のようなメッセージが表示された場合は、ビルド時のCoreライブラリのダウンロードに失敗しています。

Downloading core failed. Please try again once you have an Internet connection.

このエラーが起こるのは、下記のいずれかの理由によります。

  1. 米国輸出規制法のリストに含まれる国のIPアドレスが割り当てられている。 米国の法律に従うために、Realmは上記のリストに含まれる国で使用することはできません。 さらに詳しいことはライセンス条項をお読みください。
  2. 中国国内、あるいは国レベルのファイアウォールによってCloudFlareまたはAmazon AWS S3へのアクセスを制限している国からアクセスしている。 さらに詳しいことはこちらのIssueをご覧ください。
  3. Amazon AWS S3が障害により一時的に利用できなくなっている。AWS Service Health Dashboardを見てサービスの稼働状況を確認し、 障害が解決された後で再度やり直してください。