* Which Category is your question related to? *
Datastore
* What AWS Services are you utilizing? *
AWS AppSync,
* Provide additional details e.g. code snippets *
We are implementing an RN application using AWS Amplify, we have a schema with a many to many relationships, as suggested we used a bridge model to link the tow models
Example Schema:
type Task
@model
{
id:ID!
name: String!
icon: String
records: [RecordTask] @connection(name: "TaskRecords")
}
type TimeTrackingRecord
@model
{
id: ID!
hours: Int!
minutes: Int!
time: AWSDateTime!
building: Building @connection(name: "BuildingTimeTrackingRecords")
tasks: [RecordTask] @connection(name: "RecordTasks")
}
type RecordTask
@model
{
id: ID!
record: TimeTrackingRecord @connection(name:"RecordTasks")
task: Task @connection(name: "TaskRecords")
}
Here RecordTask is used to link both Task & TimeTrackingRecord, we used amplify codegen models to generate models for us.
To complete our logic we are doing the following using datastore
1- Creating a set of Tasks
2- Creating a TimeTrackingRecord
3- loop over tasks and creating RecordTask
Then we tried two solutions:-
1- Updating already created TimeTrackingRecord to link it to the created RecordTask => Error on the update mutation by Appsync (no reason provided)
2- Relay that Datastore will connect the Tasks and TimeTrackingRecord, but unfortunately when we try to access TimeTrackingRecord tasks it is always empty however the RecordTask already created.
Any advice how can improve our M2M model to work fine with DataStore.
Hi @moweex
Any advice how can improve our M2M model to work fine with DataStore.
Your schema is fine, it works as expected, we tested with this sample app and your schema (minus the Building
type) provisioned to a back-end.
Note the order of the saves in the addRecordTask
function:
Schema
type Task @model {
id: ID!
name: String!
icon: String
records: [RecordTask] @connection(name: "TaskRecords")
}
type TimeTrackingRecord @model {
id: ID!
hours: Int!
minutes: Int!
time: AWSDateTime!
# building: Building @connection(name: "BuildingTimeTrackingRecords")
tasks: [RecordTask] @connection(name: "RecordTasks")
}
type RecordTask @model {
id: ID!
record: TimeTrackingRecord @connection(name: "RecordTasks")
task: Task @connection(name: "TaskRecords")
}
App (.tsx)
import React, { useEffect, useState } from "react";
import "./App.css";
import { DataStore } from "@aws-amplify/datastore";
import { Task, TimeTrackingRecord, RecordTask } from "./models";
function App() {
const [recordTasks, setRecordTasks] = useState([] as RecordTask[]);
useEffect(() => {
queryRecordTasks();
return () => {};
}, []);
async function queryRecordTasks() {
const recordTasks = await DataStore.query(RecordTask);
setRecordTasks(recordTasks);
}
async function addRecordTask() {
const tasks = [];
for (let i = 0; i < 5; i++) {
const task = new Task({
name: `the task ${i + 1}`
});
await DataStore.save(task);
tasks.push(task);
}
const record = new TimeTrackingRecord({
hours: 2,
minutes: 30,
time: new Date().toISOString()
});
await DataStore.save(record);
for (const task of tasks) {
const recordTask = new RecordTask({
task,
record
});
await DataStore.save(recordTask);
}
await queryRecordTasks();
}
return (
<div className="App">
<header className="App-header">
<button onClick={addRecordTask}>Add</button>
<pre style={{ textAlign: "left" }}>
{JSON.stringify(recordTasks, null, 2)}
</pre>
</header>
</div>
);
}
export default App;
I hope this helps, let us know how it goes.
Hi @manueliglesias ,
thank you very much for your response and demo application, maybe my question was not clear enough.
Savings are working fine with our schema, we are using it the exact way you mentioned in your code. however, our issue is after saving a recordTask, if you query TimeTrackingRecord, you will find that the tasks property is always empty.
const records = await DataStore.query(TimeTrackingRecord);
for (const record of records) {
console.log(record.tasks);
}
so it seems the save function not updating the other end of the relationship, even after a sync call with appsync, tasks still empty, to get it we are doing it manually by querying RecordTask with filter.
@moweex : Hi, thank you for bring this up. Currently, we only support loading from the one part of 1:M relationship. For eq, if you have Post to Comment as 1:M relationship. You can query for the post from comment but not the other way around.
But, we are tracking this internally to eager/lazy load all many side from the one side(comments from post). We will mark this issue as feature request to track updates.
Please note, that similar problem is reported also in amplify-cli (however, it wasn't directly related to DataStore):
https://github.com/aws-amplify/amplify-cli/issues/3438
Any update on this? I was quite excited about the prospects of DataStore, it's API, and how it works, however without this ability I cannot use it at all within my app. Being able to lazy-load relations would obviously be the preferred method to match the capabilities of GraphQL, but I would even just settle for the ability to manually pull records by filtering on IDs at this point. It feels like that fix primarily involves adjusting the generated models to include the foreign ID so that the predicate filter can be used with DataStore.query
. I've got a mix of M:M and 1:M relations in my app and none of the models are outputting the foreignID field as described in the docs for querying relations.
I just ran into this issue as well.
I'm building an application for tracking books. I've got an index page that shows all of the books and their authors. Books have a M:M relationship to authors through a BookAuthor
model and I'm unsure how to correctly query these relationships.
Here's what I'm doing, my index pages queries books and then I loop over each of those books and query their BookAuthors. The code looks something like this...
books = await DataStore.query(Book)
bookAuthorQueries = books.map(book => async (
(await DataStore.query(BookAuthor)).filter(ba => ba.book.id === book.id)
))
Does this seem like the best approach for querying has many relationships?
Also, will this approach stop working once I reach a certain number of records? Are there any limits I should be aware of?
This is the first app I've written using Dynamo so sorry if this is a silly question.
Any update about this?
Is there any rough timeline for this "lazy-loading" feature?
Do we have a date for the release of the lazy loading feature? Very disappointing that such a basic feature has not been implemented and has not received attention.
Would be nice to get at least a short update about your plans....
@ryanto we're using the same approach as you (as a stop-gap?). The default page size limit on Datastore.query
is 100
, so you'll need to up that if you have more than 100 authors.
// TEMP FIX(?)
DataStore.query(BookAuthor, Predicates.ALL, {
page: 0,
limit: 1_000_000
});
Agreed on hearing of an update, I can't help but think about the serious performance hit I take when querying the join table to get data from both sides of the relationship..
@smithad15 's comment: https://github.com/aws-amplify/amplify-js/issues/5054#issuecomment-681358558 seems to best capture my thoughts on a solution as well. Seems like there would have to be another index table generated in DDB for each of the two Objects being joined in the M-M relation. Each Object's model would then include the FK for when the object is queried (this is a M-M anyways, expect a bit higher of a performance hit when querying an index for the FK)
Rather than getting both objects returned in the BookAuthor join table (see @ryanto 's https://github.com/aws-amplify/amplify-js/issues/5054#issuecomment-702275386), we would then be able to query only against the Author object without having to duplicate the effort by querying both Book and Author.
tl;dr
Make an index table for both @model objects in the M-M relationship. Avoid having to query against the JoinTable to minimize duplication, save money on query time, and improve performance.
Thoughts?
Most helpful comment
Do we have a date for the release of the lazy loading feature? Very disappointing that such a basic feature has not been implemented and has not received attention.