データの移行

コレクション、フィールド、インデックスを追加または削除すると、Isarは自動的にデータベーススキーマを移行(マイグレート)します。時には、データも一緒に移行したい場合もあるでしょう。 Isarは組み込み解決法を提供していません。これはデタラメな移行制限が課される可能性があるためです。ただ、ニーズに合った移行ロジックを簡単に実装することができます。

この例では、データベース全体で1つのバージョンを使用したいと思います。共有環境設定を使って現在のバージョンを保存し、移行したいバージョンと比較します。バージョンが一致しない場合、データを移行し、バージョンを更新します。

ヒント

各コレクションに独自のバージョンを与え、個別に移行することも可能です。

誕生日フィールドを持つユーザーコレクションがあると仮定します。このアプリのバージョン2では、年齢に基づいてユーザーを照会するために、誕生年のフィールドを追加する必要があります。

Version 1:

@collection
class User {
  late int id;

  late String name;

  late DateTime birthday;
}

Version 2:

@collection
class User {
  late int id;

  late String name;

  late DateTime birthday;

  short get birthYear => birthday.year;
}

問題は、バージョン1では birthYear フィールドが存在しないため、既存のUserモデルを作成しても空の birthYearが設定されることです。 birthYear フィールドを設定するために、データを移行する必要があります。

import 'package:isar/isar.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() async {
  final dir = await getApplicationDocumentsDirectory();
  
  final isar = await Isar.openAsync(
    schemas: [UserSchema],
    directory: dir.path,
  );

  await performMigrationIfNeeded(isar);

  runApp(MyApp(isar: isar));
}

Future<void> performMigrationIfNeeded(Isar isar) async {
  final prefs = await SharedPreferences.getInstance();
  final currentVersion = prefs.getInt('version') ?? 2;
  switch(currentVersion) {
    case 1:
      await migrateV1ToV2(isar);
      break;
    case 2:
      // バージョンが設定されていない場合(新規インストール)、または既にver.2の場合は移行する必要はない
      return;
    default:
      throw Exception('Unknown version: $currentVersion');
  }

  // バージョンを更新する
  await prefs.setInt('version', 2);
}

Future<void> migrateV1ToV2(Isar isar) async {
  final userCount = await isar.users.count();

  // すべてのユーザーを一度にメモリにロードするのを避けるため、ユーザーをページ分割する
  for (var i = 0; i < userCount; i += 50) {
    final users = await isar.users.where().offset(i).limit(50).findAll();
    await isar.writeTxn((isar) async {
      // birthYear ゲッターを使用しているため、何も更新する必要はありません
      await isar.users.putAll(users);
    });
  }
}

警告

多くのデータを移行する必要がある場合、UIスレッドに負担がかからないようにバックグラウンドアイソレートを使用することを検討してください。