モデル関係の構築
アプリケーションデータをモデル化する際、異なるデータモデル間の関係を確立する必要があることがよくあります。Amplify Data では、データスキーマに 1 対多、1 対 1、および多対多の関係を作成できます。クライアント側では、Amplify Data により関連データの遅延読み込みまたは事前読み込みが可能です。
関係のタイプ
| 関係 | コード | 説明 | 例 |
|---|---|---|---|
| 1 対多 | a.hasMany(...) & a.belongsTo(...) | 2 つのモデル間に 1 対多の関係を作成します。 | Team は複数の Members を持ちます。Member は Team に属しています。 |
| 1 対 1 | a.hasOne(...) & a.belongsTo(...) | 2 つのモデル間に 1 対 1 の関係を作成します。 | Customer は 1 つの Cart を持ちます。Cart は 1 つの Customer に属しています。 |
| 多対多 | 結合テーブル上の 2 つの a.hasMany(...) & a.belongsTo(...) | 結合テーブルで関連モデル間に 2 つの 1 対多の関係を作成します。 | Post は複数の Tags を持ちます。Tag は複数の Posts を持ちます。 |
1 対多のモデル関係を構築する
hasMany() メソッドと belongsTo() メソッドを使用して、2 つのモデル間に 1 対多の関係を作成します。以下の例では、Team は複数の Members を持ち、Member はちょうど 1 つの Team に属しています。
- Member モデルで
teamIdという参照フィールドを作成します。この参照フィールドの型は Team の識別子の型と一致する必要があります。この場合、自動生成されるid: a.id().required()フィールドです。 teamIdフィールドを参照するteamという関係フィールドを追加します。これにより、Member モデルからチーム情報を照会できます。- Member モデルの
teamIdフィールドを参照するmembersという関係フィールドを追加します。
const schema = a.schema({ Member: a.model({ name: a.string().required(), // 1. Create a reference field teamId: a.id(), // 2. Create a belongsTo relationship with the reference field team: a.belongsTo('Team', 'teamId'), }),
Team: a.model({ mantra: a.string().required(), // 3. Create a hasMany relationship with the reference field // from the `Member`s model. members: a.hasMany('Member', 'teamId'), }),}).authorization((allow) => allow.publicApiKey());「Has Many」関係をレコード間で作成する
final team = Team(mantra: "Go Frontend!");final teamRequest = ModelMutations.create(team);final teamResponse = await Amplify.API.mutate(request: teamRequest).response;
final member = Member(name: "Tim", team: teamResponse.data);final memberRequest = ModelMutations.create(member);final memberResponse = await Amplify.API.mutate(request: memberRequest).response;「Has Many」関係をレコード間で更新する
final newTeam = Team(mantra: "Go Fullstack!");final newTeamRequest = ModelMutations.create(team);final newTeamResponse = await Amplify.API.mutate(request: teamRequest).response;
final memberWithUpdatedTeam = existingMember.copyWith(team: newTeamResponse.data);final memberUpdateRequest = ModelMutations.update(memberWithUpdatedTeam);final memberUpdateResponse = await Amplify.API.mutate(request: memberUpdateRequest).response;「Has Many」関係をレコード間で削除する
参照フィールドが必須でない場合、関係値を null に設定することで、1 対多の関係を「削除」できます。
final memberWithRemovedTeam = existingMember.copyWith(team: null);final memberRemoveRequest = ModelMutations.update(memberWithRemovedTeam);final memberRemoveResponse = await Amplify.API.mutate(request: memberRemoveRequest).response;「Has Many」関係で関連データを読み込む
// Fetch the team with the team id.final teamRequest = ModelQueries.get<Team>( Team.classType, TeamModelIdentifier(id: "YOUR_TEAM_ID"));final teamResult = await Amplify.API.query(request: teamRequest).response;final team = teamResult.data!;
// Define a limit for your paginationconst limit = 100;
// Do the initial call to get the initial itemsfinal firstRequest = ModelQueries.list<Member>(Member.classType, limit: limit, where: Member.TEAMID.eq(team.id));final firstResult = await Amplify.API.query(request: firstRequest).response;final firstPageData = firstResult.data;
// If there are more than 100 items you can reiterate the following code to get next pages.if (firstPageData?.hasNextResult ?? false) { final secondRequest = firstPageData!.requestForNextResult; final secondResult = await Amplify.API.query(request: secondRequest!).response; return secondResult.data?.items ?? <Member?>[];} else { // You can return the page data by calling items property. return firstPageData?.items ?? <Member?>[];}「1 対 1」関係をモデル化する
hasOne() メソッドと belongsTo() メソッドを使用して、2 つのモデル間に 1 対 1 の関係を作成します。以下の例では、Customer は Cart を持ち、Cart は Customer に属しています。
- Cart モデルで
customerIdという参照フィールドを作成します。この参照フィールドの型は Customer の識別子の型と一致する必要があります。この場合、自動生成されるid: a.id().required()フィールドです。 customerIdフィールドを参照するcustomerという関係フィールドを追加します。これにより、Cart モデルから顧客情報を照会できます。- Cart モデルの
customerIdフィールドを参照するactiveCartという関係フィールドを追加します。
const schema = a.schema({ Cart: a.model({ items: a.string().required().array(), // 1. Create reference field customerId: a.id(), // 2. Create relationship field with the reference field customer: a.belongsTo('Customer', 'customerId'), }), Customer: a.model({ name: a.string(), // 3. Create relationship field with the reference field // from the Cart model activeCart: a.hasOne('Cart', 'customerId') }),}).authorization((allow) => allow.publicApiKey());「Has One」関係をレコード間で作成する
「has one」関係をレコード間で作成するには、最初に親アイテムを作成し、次に子アイテムを作成して親を割り当てます。
final customer = Customer(name: "Rene");final customerRequest = ModelMutations.create(customer);final customerResponse = await Amplify.API.mutate(request: customerRequest).response;
final cart = Cart(items: ["Tomato", "Ice", "Mint"], customer: teamResponse.customer);final cartRequest = ModelMutations.create(cart);final cartResponse = await Amplify.API.mutate(request: cartRequest).response;「Has One」関係をレコード間で更新する
「Has One」関係をレコード間で更新するには、最初に子アイテムを取得し、次に親への参照を別の親に更新します。たとえば、Cart を別の Customer に再割り当てするには:
final newCustomer = Customer(name: "Ian");final newCustomerRequest = ModelMutations.create(newCustomer);final newCustomerResponse = await Amplify.API.mutate(request: newCustomerRequest).response;
final cartWithUpdatedCustomer = existingCart.copyWith(customer: newCustomerResponse.data);final cartUpdateRequest = ModelMutations.update(cartWithUpdatedCustomer);final cartUpdateResponse = await Amplify.API.mutate(request: cartUpdateRequest).response;「Has One」関係をレコード間で削除する
final cartWithRemovedCustomer = existingCart.copyWith(customer: null);final cartRemoveRequest = ModelMutations.update(cartWithRemovedCustomer);final cartRemoveResponse = await Amplify.API.mutate(request: cartRemoveRequest).response;「Has One」関係で関連データを読み込む
// Fetch the cart with the cart id.final cartRequest = ModelQueries.get<Cart>( Cart.classType, CartModelIdentifier(id: "MY_CART_ID"));final cartResult = await Amplify.API.query(request: cartRequest).response;final cart = cartResult.data!;
// Do the customer call to with the id from cartif (cart.customerId != null) { final customerRequest = ModelQueries.get<Customer>( Customer.classType, CustomerModelIdentifier(id: cart.customerId!)); final customerResult = await Amplify.API.query(request: customerRequest).response; final customer = customerResult.data!;}「多対多」関係をモデル化する
2 つのモデル間に多対多の関係を作成するには、「結合テーブル」として機能するモデルを作成する必要があります。この「結合テーブル」は、2 つの関連エンティティ間に 2 つの 1 対多の関係を含む必要があります。たとえば、Post が多くの Tags を持ち、Tag が多くの Posts を持つという関係をモデル化するには、これら 2 つのエンティティ間の関係を表す新しい PostTag モデルを作成する必要があります。
const schema = a.schema({ PostTag: a.model({ // 1. Create reference fields to both ends of // the many-to-many relationship postId: a.id().required(), tagId: a.id().required(), // 2. Create relationship fields to both ends of // the many-to-many relationship using their // respective reference fields post: a.belongsTo('Post', 'postId'), tag: a.belongsTo('Tag', 'tagId'), }), Post: a.model({ title: a.string(), content: a.string(), // 3. Add relationship field to the join model // with the reference of `postId` tags: a.hasMany('PostTag', 'postId'), }), Tag: a.model({ name: a.string(), // 4. Add relationship field to the join model // with the reference of `tagId` posts: a.hasMany('PostTag', 'tagId'), }),}).authorization((allow) => allow.publicApiKey());2 つのモデル間の複数の関係をモデル化する
関係は参照フィールドによって一意に定義されます。たとえば、Post は Person モデルとの author と editor の個別の関係を持つことができます。
const schema = a.schema({ Post: a.model({ title: a.string().required(), content: a.string().required(), authorId: a.id(), author: a.belongsTo('Person', 'authorId'), editorId: a.id(), editor: a.belongsTo('Person', 'editorId'), }), Person: a.model({ name: a.string(), editedPosts: a.hasMany('Post', 'editorId'), authoredPosts: a.hasMany('Post', 'authorId'), }),}).authorization((allow) => allow.publicApiKey());クライアント側では、次のコードで関連データを取得できます:
const client = generateClient<Schema>();
const { data: post } = await client.models.Post.get({ id: "SOME_POST_ID" });
const { data: author } = await post?.author();const { data: editor } = await post?.editor();識別子でソートキーを持つモデルのモデル関係
データモデルが識別子でソートキーを使用する場合、関連データモデルにも参照フィールドを追加し、ソートキーフィールドを保存する必要があります:
const schema = a.schema({ Post: a.model({ title: a.string().required(), content: a.string().required(), // Reference fields must correspond to identifier fields. authorName: a.string(), authorDoB: a.date(), // Must pass references in the same order as identifiers. author: a.belongsTo('Person', ['authorName', 'authorDoB']), }), Person: a.model({ name: a.string().required(), dateOfBirth: a.date().required(), // Must reference all reference fields corresponding to the // identifier of this model. authoredPosts: a.hasMany('Post', ['authorName', 'authorDoB']), }).identifier(['name', 'dateOfBirth']),}).authorization((allow) => allow.publicApiKey());関係を必須または任意にする
Amplify Data の関係は参照フィールドを使用して、関係が必須か任意かを判断します。参照フィールドを必須としてマークした場合、2 つのモデル間の関係を「削除」することはできません。代わりに、関連レコード全体を削除する必要があります。
const schema = a.schema({ Post: a.model({ title: a.string().required(), content: a.string().required(), // You must supply an author when creating the post // Author can't be set to `null`. authorId: a.id().required(), author: a.belongsTo('Person', 'authorId'), // You can optionally supply an editor when creating the post. // Editor can also be set to `null`. editorId: a.id(), editor: a.belongsTo('Person', 'editorId'), }), Person: a.model({ name: a.string(), editedPosts: a.hasMany('Post', 'editorId'), authoredPosts: a.hasMany('Post', 'authorId'), }),}).authorization((allow) => allow.publicApiKey());