0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MoorでのForeign Key設定方法

Posted at

FlutterのデータベースライブラリのMoorを選択する方がいると思いますが、今記事では、どのように"Foreign Key"を設定するのか、1対多、多対多はMoorでどう実現するのかを見ていきます。

対象読者

  • moorの設定方法がある程度理解できている人。
  • moorのテーブル作成をしたことがある人。
  • daoでクエリを記述したことのある人。(知らなくても大丈夫かも)
  • 外部キーの方法で困っている人。

※今回は、基本的なmoorのことは省いています。(コード自動生成など)

1. データベース設定

初めにデータベースの設定を行なっていきます。
この設定は、moorの公式ホームページを見てもらったら簡単に設定できると思います。
Moor公式HP

database.dart
@UseMoor(tables: [CategoryRecords, SubCategoryRecords], daos: [DaoQuery])
class MyDatabase extends _$MyDatabase {
  MyDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;

   @override
   MigrationStrategy get migration => MigrationStrategy(
      onCreate: (detail) {
              //一番初めにアプリを明け、データベースにアクセスするタイミングで呼ばれる。
       //この記述なくても発火してくれるため、書く必要なし。
        return detail.createAll();
      },
      //毎回アプリを明けデータベースにアクセスするタイミングで呼ばれる。
      beforeOpen: (details) async {
        await customStatement('PRAGMA foreign_key = ON');
      }
      });
}

LazyDatabase _openConnection() {

  return LazyDatabase(() async {

    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return VmDatabase(file);
  });
}

moorに慣れている人にはお馴染みのコードですが、MigrationStrategyなるものが追加されています。
onCreate関数は、「1番初めのアプリ起動時かつ、データベースに初めてアクセスするタイミング」で呼ばれます。なので、一度アプリをキルし、2度目アプリを開き、初めてデータベースにアクセスした際は呼ばれません。

beforeOpen関数は、「アプリを起動しデータベースに初めてアクセスするタイミング」で必ず呼ばれるものになります。
この関数の場合は、一度アプリをキルし、2度目アプリを開き、初めてデータベースにアクセスした際は呼ばれます。

beforeOpen関数内で、

customStatement('PRAGMA foreign_key = ON');

との記述があります。
sqliteはデフォルトで外部キーが設定されていない(OFF)ようなので、このデータベースに初めてアクセスするタイミングで、外部キー制約をONにしなければいけないようです。

これで、下準備は完了です。

次に実際にテーブルを記述していきます。

2. テーブル作成。

今回は、
主テーブル: CategoryRecords
小テーブル: SubCategoryRecords
の二つのテーブルを作成し、1対多の関係を作っていきます。

category_records.dart
class CategoryRecords extends Table {
  IntColumn get id => integer().autoIncrement()();

  TextColumn get name => text().withLength(max: 100)();

  RealColumn get experience => real()();
}
sub_category_records.dart
class SubCategoryRecords extends Table {
  IntColumn get id => integer().autoIncrement()();

  TextColumn get name => text().withLength(max: 100)();

  IntColumn get level => integer()();

  BoolColumn get nowLevel => boolean()();

  IntColumn get categoryId =>
      integer().customConstraint('NOT NULL REFERENCES categoryRecords(id)')();
}

SubCategoryRecordテーブルに外部キーを設定しています。
moor自体には、dartのコードで外部キー制約することができないみたいなので、

integer.customConstraint('NOT NULL REFERENCES categoryRecords(id)')();

のように、customConstraintを使用して、独自で外部キー制約をカスタムしないといけないようです。

これで、SubCategoryRecordsは、CategoryRecordsのidを外部キーとしてもつことができました。

その後、コード生成してもらえたら大丈夫です。

3,データ取得クエリ(例)

それでは、コード生成し終え、テーブル内にすでにデータが入っていることを想定して2つのテーブルをjoinしデータを取得する方法を見ていきましょう。

クエリは、daoを使用し別Classで記述しています。

dao_query.dart

import 'package:demo_flutter_project/models/db/practice/database.dart';
import 'package:demo_flutter_project/models/db/practice/tables/category_records.dart';
import 'package:demo_flutter_project/models/db/practice/tables/sub_category_records.dart';
import 'package:moor/moor.dart';

part 'dao_query.g.dart';

//2つのテーブルから取得できたデータを格納するClassを作成。
class MainAndSubCategory {
  final CategoryRecord categoryRecord;
  final SubCategoryRecord subCategoryRecord;

  MainAndSubCategory(this.categoryRecord, this.subCategoryRecord);
}

class DaoQuery extends DatabaseAccessor<MyDatabase> with _$DaoQueryMixin {
  DaoQuery(MyDatabase db) : super(db);

  //2つのテーブルをjoinしてデータを取得する方法。
  Future<List<MainAndSubCategory>> getAllMainAndSubCategory() async {
    //subCategoryRecordsテーブルを軸にjoinメソッドでcategoryRecordsをjoinする。
    final query = select(subCategoryRecords).join([
      //categoryRecordsのidとsubCategoryRecordsのcategoryIdが一致しているものに対して。
      //leftOuterとrightOuterも用意されている。
      innerJoin(categoryRecords,
          categoryRecords.id.equalsExp(subCategoryRecords.categoryId))
    ]);
        
         //queryを使用し、getでデータ取得。(get()もしくは、watch()でデータを取得可能。)
    final typedResult = await query.get();
    final List<MainAndSubCategory> categoryList = [];
    //mapではできなかった。(理由は詳しい方教えてください。。。)
    for (var result in typedResult) {
      categoryList.add(MainAndSubCategory(
          //以下のように、それぞれのテーブルから取得してきたデータを取得できる。
          result.readTable(categoryRecords),
          result.readTable(subCategoryRecords)));
    }
    return categoryList;
  }
}

クエリの記述方法はこんな形になります。
queryを使用してgetする。。。
あたりからは、人によって記述方法が変わってくると思います。

今回は、get()で取得したのですが、watchでもデータ取得できますが、公式にガッツリ方法が記述されているので今回はget()で記述しました。

終わり

今回はmoorを使用して外部キー制約を見ていきました。
自分もmoorはあまり触ったことがなく、間違っている部分があると思いますのでご指摘お願いいたします。

また、seed値のようなものをmoorで作成できる方法があればご教授願いたいです。

ありがとうございました。

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?